[Add] Serve a Gutenberg site in a real WordPress via Playground (#251) - #261
Open
juanmaguitar wants to merge 1 commit into
Conversation
juanmaguitar
force-pushed
the
juanmaguitar/gutenberg-playground-serve
branch
from
August 11, 2026 10:57
1797b96 to
3d5cd8b
Compare
A Core site is served by mounting its build/ as the WordPress docroot, because that build IS a WordPress install. A Gutenberg checkout is a plugin, not a WordPress, so the same path serves an empty site. Make the serve strategy follow the site's project type. - src/playground-plan.cjs: a pure planPlaygroundLaunch(strategy) that returns the @wp-playground/cli mount/install options — 'docroot' mounts build/ as /wordpress and skips the download (Core, unchanged), 'plugin-mount' lets Playground install a stock WordPress and mounts the checkout as an active plugin under wp-content/plugins/<slug> (Gutenberg). This mirrors the CLI's own --auto-mount handling (a `mount` plus an `activatePlugin` step). - server-runner.js now takes a JSON serve config and builds its runCLI options from the plan, keeping the debug + SMTP constants it adds from its own env. - playground:start resolves the strategy from the site's type and hands the runner the checkout to mount (Gutenberg) or the build dir to serve (Core). Verified the plugin-mount path end to end against a throwaway plugin: Playground downloads a stock WordPress, mounts the plugin, and reports a URL that serves WordPress. Core's docroot path is byte-identical. Known gap: the debug.log tail reads a host-side build/wp-content/debug.log, which a plugin-mounted (VFS) WordPress does not write — so the debug-log panel is empty for a Gutenberg site. It degrades quietly (the tail no-ops on a missing file) rather than failing; a Gutenberg-aware debug path is a follow-up. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
juanmaguitar
force-pushed
the
juanmaguitar/gutenberg-playground-serve
branch
from
August 11, 2026 13:49
3d5cd8b to
3e81245
Compare
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.
Why
Part of #251, and the piece that makes a Gutenberg site actually usable: seeing a Gutenberg
change running in a real WordPress. A Core site is served by mounting its
build/as theWordPress docroot, because that build is a WordPress install. A Gutenberg checkout is a plugin,
not a WordPress — mount it as a docroot and you get an empty site. So the serve strategy has to
follow the site's project type.
What changes
src/playground-plan.cjs(new, pure) —planPlaygroundLaunch(strategy)returns the@wp-playground/climount/install options:docroot(Core, unchanged): mountbuild/as/wordpress,wordpressInstallMode: install-from-existing-files-if-needed(skip the download; the build already is WordPress).plugin-mount(Gutenberg): leave the install mode at the default so Playground downloads andinstalls a stock WordPress, then
mountthe checkout under/wordpress/wp-content/plugins/<slug>and add anactivatePluginstep. This mirrors@wp-playground/cli's own--auto-mountplugin handling (verified against the CLI source).server-runner.jsnow takes a JSON serve config (argv[2]) and builds itsrunCLIoptions fromthe plan, still adding the debug + SMTP constants from its own environment. The loopback / hide-
window patches still run before the CLI is required.
playground:startresolves the strategy from the site's project type and hands the runner thecheckout to mount (Gutenberg) or the build dir to serve (Core).
How to test this
Platforms: any. Requires a Gutenberg site (from #255) that has been installed and built.
Automated / mechanism check (no full Gutenberg build needed):
.phpplugin header.node src/server-runner.js '{"strategy":"plugin-mount","pluginDir":"<that dir>","pluginSlug":"smoke"}'.→ It logs
Mount … → /wordpress/wp-content/plugins/smoke, installs a stock WordPress, and printsSERVER_URL:http://127.0.0.1:9400/; opening it serves WordPress. (This is exactly what I ran toverify the plugin-mount path end to end.)
Full flow (a real Gutenberg site):
npm run build).wp-admin → the block editor reflects the built Gutenberg, not core's bundled version.
What must not have happened:
build/as/wordpresswithinstall-from-existing-files-if-needed; the emitted options match the pre-change ones (emptymount/additional-blueprint-stepsare no-ops).test/ipc-wiring.test.cjspins the docrootconfig for a no-type site.
test/runner-wiring.test.cjs), so thedev site is never briefly served to the LAN.
exit(1)s and the start request settleswith an error rather than waiting out the 120s timeout.
What cannot be tested in the suite: the live WASM-PHP boot (network download of stock WP,
minutes on a slow machine). The suite unit-tests the pure planner instead; the boot was verified by
hand as above. Automated:
npm run lintclean, 804 tests pass.Risks and limitations
build/wp-content/debug.log; a plugin-mounted (VFS) WordPress does not write there. It degradesquietly — the watcher no-ops on the missing file/dir, no crash — rather than failing. A
Gutenberg-aware debug path is a follow-up.
pinning a WP version is a future option if a mismatch ever surfaces.
Related
Part of #251. Stacked on #255.
Review outcome (required — see AGENTS.md)
0 [fix here] · 1 [follow-up]. Ran the review in
.github/instructions/code-review.instructions.md; judgement pass in a fresh subagent. Lint clean,804 tests pass.
build/wp-content/debug.log) isCore-shaped; a plugin-mounted Gutenberg WordPress doesn't write there. Verified it degrades
gracefully (watcher no-ops on the missing file/dir), so the panel is inert rather than broken.
Documented above; a Gutenberg-aware debug path rides with the later copy/UX work.
Core docroot option set is behaviorally identical (empty
mount/additional-blueprint-stepsareno-ops,
path.resolvedrop is harmless since the path is already absolute); failure paths areloud (runner
exit(1)settles the start request, no silent hang); untrusted-input handling isfine (JSON built from a registered path,
shell:false); VFS guest paths use POSIX templates whilehost paths use
path.join; tests pin both forks and would fail on a regression.Implementation notes
The plugin-mount recipe is not invented — it's the exact shape
@wp-playground/clibuilds for aplugin passed to
--auto-mount: amountof the host dir to/wordpress/wp-content/plugins/<slug>plus an
{ step: 'activatePlugin', pluginPath }inadditional-blueprint-steps, with the default(download-and-install) WordPress. Keeping the planner pure (
src/playground-plan.cjs) is what letsthe fork be unit-tested without booting WASM PHP;
server-runner.jsmerges the plan with theenv-derived debug/SMTP constants and spawns.