Forward host stdin to wp-cli through the server daemon#3211
Open
Forward host stdin to wp-cli through the server daemon#3211
Conversation
`echo foo | studio wp eval 'echo file_get_contents("php://stdin");'`
silently dropped input when the site was running (daemon path). Both
paths now drain process.stdin up-front and forward the bytes to
`php.cli({ stdin })`, which — paired with the Playground-side stdin
option — surfaces them to PHP via `php://stdin`.
- commands/wp.ts: `drainHostStdin()` reads a non-TTY process.stdin
into a Buffer and passes it to both the daemon (`sendWpCliCommand`)
and in-proc (`runWpCliCommand`/`runGlobalWpCliCommand`) paths.
- lib/types/wordpress-server-ipc.ts: wp-cli-command IPC payload gains
an optional `stdinBase64` field. Base64 is used because Node's
child_process IPC is JSON-serialized; we must preserve binary bytes
(e.g. gzipped SQL dumps) byte-for-byte.
- lib/wordpress-server-manager.ts: `sendWpCliCommand` accepts an
optional Buffer and base64-encodes it into the payload.
- lib/run-wp-cli-command.ts: in-proc `runWpCliCommand` /
`runGlobalWpCliCommand` accept an optional `{ stdin }` and
forward it to `php.cli()`.
- wordpress-server-child.ts: decode the base64 on the daemon side and
hand the bytes to `server.playground.cli(args, { stdin })`. Also
disable the sequential() dedup key whenever stdin is present —
piped stdin is inherently non-idempotent and two callers with
coincidentally equal byte lengths must not collapse into one
execution. The previous key included byte length only, which was
not enough.
No behavior change for commands invoked without piped stdin.
Collaborator
📊 Performance Test ResultsComparing 445d9a1 vs trunk app-size
site-editor
site-startup
Results are median values from multiple test runs. Legend: 🟢 Improvement (faster) | 🔴 Regression (slower) | ⚪ No change (<50ms diff) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
studio wp <command>now forwards piped host stdin to PHP. Closes #3200.Why
studio wpruns in a different process from the WordPress server daemon (or in a worker thread on the in-proc path). Neither has the user's stdin pipe attached, so anything piped on the host never reached PHP.php://stdinalways read empty.How
Drain host stdin up-front in
commands/wp.ts—drainHostStdin()readsprocess.stdininto a Buffer when non-TTY (interactive shells are never blocked). Forwarded to both the daemon path (sendWpCliCommand) and the in-proc path (runWpCliCommand/runGlobalWpCliCommand).Base64 over IPC — Node's
child_processIPC serializes payloads as JSON, so thewp-cli-commandIPC message gets an optionalstdinBase64field to preserve binary bytes (gzipped SQL dumps, ZIP files, etc.) untouched.Daemon decodes + forwards — the daemon child in
wordpress-server-child.tsdecodes base64 and hands the bytes toserver.playground.cli(args, { stdin }).Disable dedup when stdin is present —
sequential()previously deduped concurrent WP-CLI calls by args alone. Two callers piping different bytes into the same command would coincidentally collapse into one execution, with the second caller getting the first caller's result silently. Fixed by returningundefinedfromdeduplicateKeywhenever stdin is present. Piped stdin is inherently non-idempotent; dedup is meaningless for it.Requires
This PR relies on the new
stdinoption onPHP.cli()added in WordPress/wordpress-playground#3523. Merge order: Playground first → release → Studio bumps the version pin → this PR merges.Tests
Validated end-to-end against a real Studio site:
wp post create - --post_title=...creates posts with piped markdown contentNo behavior change when stdin is a TTY
drainHostStdin()returnsundefinedwhenprocess.stdin.isTTY, so interactive invocations are unaffected. Existing args-based dedup still applies to no-stdin calls (only disabled when bytes are present).Files
apps/cli/commands/wp.ts—drainHostStdin(), forward to both pathsapps/cli/lib/run-wp-cli-command.ts— in-proc paths accept{ stdin }, forward tophp.cli()apps/cli/lib/types/wordpress-server-ipc.ts— add optionalstdinBase64to IPC schemaapps/cli/lib/wordpress-server-manager.ts—sendWpCliCommandacceptsBuffer, base64-encodes itapps/cli/wordpress-server-child.ts— decode base64, forward toserver.playground.cli({ stdin }), fix dedup keyAI assistance
stdinBase64schema, daemon decode,runWpCliCommandpassthrough), and thesequential()dedup-key fix. The dedup-collision bug was surfaced by a parallel stress test during review and fixed in the same branch. All code was read, understood, and exercised end-to-end (100 sequential varied stdin, 20 parallel same-length-different-content, 5×1MB md5 roundtrip, alternating stdin/no-stdin, binary/UTF-8/empty/huge payloads) before submission.