Commit 70a320f
authored
fix: eval (#383)
## Summary
Previously, `react-server` and `react-server build` would silently treat
piped stdin as the server entrypoint whenever `fd 0` happened to be a
pipe, file redirect, or (in production) any non-TTY context outside CI.
That made the CLI's behavior
depend on invisible environmental state: the same command could behave
differently depending on whether it was invoked from a shell, an editor
task runner, a process manager, or a subprocess whose parent happened to
close stdin. It also meant
stdin could be read without the user ever asking for it, which is
surprising at best and unsafe at worst.
This change makes `--eval` the single, explicit opt-in. Stdin is now
**only** consulted when the user passes `--eval`, and nothing else
changes the CLI's entrypoint resolution.
## Behavior
`--eval "<code>"` passes the inline string as the virtualized server
entrypoint, the same as before. `--eval` with no value reads the full
entrypoint from stdin — this is the new, explicit way to pipe code in.
With no `--eval` at all, stdin is
never touched, even if it is a pipe or a file redirect; the CLI falls
back to the positional root or the file-router as it always did.
The JS-level signal is that the plugin's `options.eval` is now
tri-valued: `string` for inline code, `true` for "read stdin", and
anything else (undefined / `false`) for "don't use eval at all". The CLI
option definition changed from `-e,
--eval <code>` (required value) to `-e, --eval [code]` (optional value)
in both `dev` and `build` commands to support the bare form.
## Why
The previous auto-detection had three concrete problems. It could
consume stdin from an unrelated parent process that happened to leave fd
0 open as a pipe, producing confusing "Root module not provided" errors
or worse, running
attacker-controlled code. It was non-deterministic across environments:
the production path additionally gated on `!process.env.CI`, so the same
invocation would take different branches in CI vs. local. And it made
the `--eval` flag's contract
ambiguous, because stdin could be the entrypoint even when `--eval` was
absent, so users couldn't reason about the CLI from the flags alone.
Making stdin opt-in via `--eval` collapses these three cases into one
rule that is trivial to explain and impossible to trigger by accident.
## Changes
`lib/plugins/react-server-eval.mjs` drops the `fstatSync`-based
`isStdinPiped` probe entirely. The load handler now branches cleanly on
`options.eval`: string → return it verbatim, `true` → read stdin,
otherwise → return the "Root module not
provided" stub. `lib/dev/action.mjs` no longer runs its own `fstat` on
fd 0 to pick the virtual entrypoint; it selects the virtual module only
when `options.eval != null && options.eval !== false`.
`lib/build/server.mjs` drops the
`!process.stdin.isTTY && !process.env.CI` heuristic at the production
root-module decision and uses the same explicit check.
`bin/commands/dev.mjs` and `bin/commands/build.mjs` change the cac
option shape to `-e, --eval [code]` with clarified
help text describing both forms.
## Docs
Both the English and Japanese `features/cli.mdx` have been updated for
the dev and build `--eval` sections. The prose explicitly calls out that
stdin is never auto-consumed, and each section now carries three
runnable examples: inline code,
bare `--eval` with a pipe, and bare `--eval` with a shell file redirect.
## Tests
Two new specs guard the contract at complementary levels.
`test/__test__/react-server-eval.spec.mjs` is a fast unit spec that
imports the `react-server:eval` plugin directly and exercises its load
hook with a *poisoned* `process.stdin` that
throws on read. It verifies that the "no `--eval`", "`eval: false`", and
"`--eval "<inline>"`" paths all return the expected result without ever
touching stdin, and that `eval: true` correctly reads both single-chunk
and multi-chunk stdin
payloads. The poisoned-stdin trick is what lets the test make a positive
assertion about the *absence* of stdin reads, which a black-box HTTP
test cannot observe.
`test/__test__/cli-eval.spec.mjs` is an end-to-end spec that spawns the
real CLI binary as a subprocess. In dev mode it covers three cases:
`--eval "<inline>"` serves the inline marker, bare `--eval` + piped
stdin serves the stdin marker, and
a positional root file + piped *invalid* JavaScript serves the
positional marker (the critical regression guard — if auto-eval ever
comes back, the bogus stdin would either crash the server or be rendered
instead of the positional root). In
production mode it runs `build --eval "<inline>"` with bogus stdin piped
in and asserts the build exits successfully, covering the
`build/server.mjs` code path. The subprocess helper forces `NO_COLOR=1`
and strips ANSI defensively so readiness
detection isn't broken by colored log output.
## Compatibility
This is a behavior change that could in principle affect users who were
relying on the undocumented auto-stdin path. In practice, the documented
way to pipe code was always `--eval`, and the auto path was an
implementation detail of the
plugin. Users who were piping code without `--eval` need to add the
flag. No API surface outside the CLI is affected.1 parent 6b845f6 commit 70a320f
9 files changed
Lines changed: 524 additions & 44 deletions
File tree
- docs/src/pages
- en/(pages)/features
- ja/(pages)/features
- packages/react-server
- bin/commands
- lib
- build
- dev
- plugins
- test/__test__
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
140 | 140 | | |
141 | 141 | | |
142 | 142 | | |
143 | | - | |
| 143 | + | |
| 144 | + | |
| 145 | + | |
| 146 | + | |
| 147 | + | |
| 148 | + | |
| 149 | + | |
| 150 | + | |
| 151 | + | |
| 152 | + | |
| 153 | + | |
| 154 | + | |
144 | 155 | | |
145 | 156 | | |
146 | 157 | | |
| |||
259 | 270 | | |
260 | 271 | | |
261 | 272 | | |
262 | | - | |
| 273 | + | |
| 274 | + | |
| 275 | + | |
| 276 | + | |
| 277 | + | |
| 278 | + | |
| 279 | + | |
| 280 | + | |
| 281 | + | |
| 282 | + | |
| 283 | + | |
| 284 | + | |
263 | 285 | | |
264 | 286 | | |
265 | 287 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
140 | 140 | | |
141 | 141 | | |
142 | 142 | | |
143 | | - | |
| 143 | + | |
| 144 | + | |
| 145 | + | |
| 146 | + | |
| 147 | + | |
| 148 | + | |
| 149 | + | |
| 150 | + | |
| 151 | + | |
| 152 | + | |
| 153 | + | |
| 154 | + | |
144 | 155 | | |
145 | 156 | | |
146 | 157 | | |
| |||
259 | 270 | | |
260 | 271 | | |
261 | 272 | | |
262 | | - | |
| 273 | + | |
| 274 | + | |
| 275 | + | |
| 276 | + | |
| 277 | + | |
| 278 | + | |
| 279 | + | |
| 280 | + | |
| 281 | + | |
| 282 | + | |
| 283 | + | |
| 284 | + | |
263 | 285 | | |
264 | 286 | | |
265 | 287 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
25 | 25 | | |
26 | 26 | | |
27 | 27 | | |
28 | | - | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
29 | 32 | | |
30 | 33 | | |
31 | 34 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
24 | 24 | | |
25 | 25 | | |
26 | 26 | | |
27 | | - | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
28 | 31 | | |
29 | 32 | | |
30 | 33 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
344 | 344 | | |
345 | 345 | | |
346 | 346 | | |
347 | | - | |
| 347 | + | |
348 | 348 | | |
349 | 349 | | |
350 | 350 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
1 | | - | |
2 | 1 | | |
3 | 2 | | |
4 | 3 | | |
| |||
132 | 131 | | |
133 | 132 | | |
134 | 133 | | |
135 | | - | |
136 | | - | |
137 | | - | |
138 | | - | |
139 | | - | |
140 | | - | |
141 | | - | |
142 | | - | |
143 | | - | |
144 | | - | |
145 | | - | |
146 | | - | |
147 | | - | |
148 | | - | |
149 | | - | |
150 | | - | |
151 | | - | |
| 134 | + | |
| 135 | + | |
| 136 | + | |
152 | 137 | | |
153 | | - | |
| 138 | + | |
154 | 139 | | |
155 | 140 | | |
156 | 141 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
1 | | - | |
2 | | - | |
3 | | - | |
4 | | - | |
5 | | - | |
6 | | - | |
7 | | - | |
8 | | - | |
9 | | - | |
10 | | - | |
11 | | - | |
12 | | - | |
13 | | - | |
14 | | - | |
15 | | - | |
16 | | - | |
17 | 1 | | |
18 | 2 | | |
19 | 3 | | |
| |||
30 | 14 | | |
31 | 15 | | |
32 | 16 | | |
33 | | - | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
34 | 21 | | |
35 | | - | |
| 22 | + | |
| 23 | + | |
36 | 24 | | |
37 | 25 | | |
38 | 26 | | |
| |||
0 commit comments