Skip to content

Playground CLI: Use Blueprints v2 #2281

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 100 commits into
base: trunk
Choose a base branch
from

Conversation

adamziel
Copy link
Collaborator

@adamziel adamziel commented Jun 16, 2025

Motivation for the change, related issues

Changes the Blueprint runner in Playground CLI from the TypeScript v1 runner to the PHP Blueprints v2 runner.

Implementation details

This PR ships the blueprints.phar runner from the php-toolkit repo and a TypeScript function runBlueprintV2() that runs the PHP runner with the specified options.

CLI flags passthrough

The data flow is:

flowchart LR
        A(["bun cli.ts"])
        A --> B("runCLI()")
        B --> E("bootRequestHandler()")
        E --> C("runBlueprintV2()")
        C --> D("startServer()")
Loading
  • cli.ts just calls the runCLI() function that can be used by any frontend – CLI script, Studio, etc.
  • runCLI() parses the CLI flags, infers the PHP version to use, boots PHP, handles mounts
  • runCLI() then also passes information to the Blueprints v2 runner and starts it
  • Blueprints v2 handle all the validation, downloads, HTTP caching, WordPress installation etc. Error and progress information are passed to the JavaScript process and then reported to the user
  • At the end of it, typically we start a server. It's an Express.js server to get some multiprocessing – PHP dev server can only handle one request at a time. In the future we could support frankenphp

CLI options

wp-playground CLI

wp-playground <command> [options]

  • Commands

    • server – start the local server
    • run-blueprint – execute a Blueprint
    • build-snapshot – build a ZIP snapshot
  • General

    • --outfile <path> – output file (default wordpress.zip)
    • --port <number> – server port (default 9400)
    • --php <version> – PHP runtime (overrides Blueprint)
    • --blueprint <path|url> – Blueprint to run
    • --quiet – silence logs
    • --debug – dump PHP errors on boot failure
    • --auto-mount – detect and mount the current working directory
  • Mounting

    • --mount <host:vfs> – mount after WP install (repeatable)
    • --mount-before-install <host:vfs> – mount before WP install (repeatable)
    • --mount-dir "<host>" "<vfs>" – two-arg mount after WP install (repeatable)
    • --mount-dir-before-install "<host>" "<vfs>" – two-arg mount before WP install (repeatable)
  • Blueprint execution

    • --mode <create-new-site|apply-to-existing-site|mount-only> – execution mode (default create-new-site)
    • --db-engine <sqlite|mysql> – database engine (default sqlite)
    • --db-host <host> – MySQL host
    • --db-user <user> – MySQL user
    • --db-pass <pass> – MySQL password
    • --db-name <name> – MySQL database
    • --db-path <file> – SQLite file path
    • --truncate-new-site-directory – wipe target directory first before creating a new site. Useful to avoid a "directory must be empty" error
    • --allow <bundled-files,follow-symlinks> – extra permissions to access the local filesystem
  • Experimental

    • --experimental-multi-worker [N] – enable multi-worker; omit N to use CPU-1
    • --experimental-trace – verbose runtime trace
  • Hidden

    • --wp <version> – WordPress core version override (default latest)
    • --login – auto-login user

Combine flags as needed; repeatable ones accept multiple instances.

Simplified logic

The logic handled by the v2 runner was removed from the Playground CLI:

  • Downloading WordPress
  • Downloading the SQLite plugin
  • Caching requests
  • Calculating progress
  • Managing permissions
  • ... way more ...

The CLI is now, pretty much, just a thin glue that initializes all the components (PHP, Express.js server, v2 runner) and

Weak spots

  • Developer experience. I'm not sure about the ergonomics of Playground CLI (even before this PR). Let's discuss it. For example, there's many variants of --mount and you have to know to mount a local directory as /wordpress before the WP installation to start a site in your host filesystem. Maybe that's fine and CLI is just a low-level tool to be wrapped by something like wp-now. But maybe we can find a more convenient usage pattern without sacrificing configurability.
  • inferPHP(). Blueprint.json tells us which PHP version to load so we need to parse it before booting the Blueprint runner. This is easy for local and remote JSON files, but I'm not willing to duplicate all the zip, git, etc. parsing logic. More complex use-cases require the user to provide an explicit PHP version as a CLI option. I'm not too happy about that but I also don't have a better solution. In Node.js we could parse the Blueprint using any PHP version, but in the browser it would mean downloading additional 10MB or so just to parse the bundle – pretty expensive.
  • Logging. The logging logic seems flaky. We log some information twice, other information sometimes gets lost. We need a thorough review and a reliable reporting chain – otherwise we'll either fail to report PHP errors happening 3 workers deep, or flood the screen with a torrent of useless details.

Backwards Compatibility Break

  • This PR replaces the previous Blueprints runner. It doesn't add the new one behind a feature flag. Anyone relying on Blueprints v1 should stick to the older release of the @wp-playground/cli package.
  • These deprecated Blueprints v1 keys aren't supported anymore: request step, features, phpExtensionBundles
  • These Blueprints v1 keys aren't supported yet: landingPage
  • Not all Blueprints v2 features are implemented by the runner yet. Most of them are, but advanced content imports are a weak spot of this release.
  • The CLI options changed significantly. The old @wp-playground/cli commands will not work with the new version. I wonder if we could continue supporting the "legacy" commands 🤔

Follow-up work

  • Integrate the v2 runner with Playground web
    • Trim down the blueprints.phar release used by Playground to ~100KB (it's absolutely possible, e.g. by using JavaScripts's new URL() instead of shipping an entire URL parser).
    • Ship the data-liberation.phar importer separately from the blueprints.phar release.
    • Remove all the Blueprints v1 TypeScript logic – step handlers, the compile() function, progress tracking etc.
  • An npm script / GitHub action to refresh the blueprints.phar release. Alternatively, embed php-toolkit as a subtree or a submodule within this repo.
  • Move auto-mount directory structure detection to the Blueprints runner. Support treating any file tree (directory, zip, git repo, WordPress export etc.) as a Blueprint based on the detection logic

Testing Instructions (or ideally a Blueprint)

A simple way to try it out is with the following command:

bun packages/playground/cli/src/cli.ts server --blueprint=https://raw.githubusercontent.com/wordpress/blueprints/refs/heads/trunk/blueprints/stylish-press/blueprint.json  --login

I'll provide a more thorough test plan once we merge #2231 and #2248.

hidden: true,
})
// Blueprint CLI options
.option('mode', {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could we make the name more clear?
Something like blueprint-mode?

Copy link
Collaborator Author

@adamziel adamziel Jun 20, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Potentially. This option name is copied frmo the blueprints.php runner but maybe a different name would be more useful here. Or in both places? I'm now thinking --site-type=new|existing|none

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's good that --site-type=new|existing|none is more explicit.

Instead of mode to declaring an execution personality, site-type declares something about the site itself and lets Playground CLI judge what behavior is appropriate.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good call! I'll address this in a follow-up PR as it will require an accompanying change in the Blueprints library

@bgrgicak
Copy link
Collaborator

bgrgicak commented Jun 18, 2025

@adamziel I explored using Playground CLI snapshots in tests and found a few issues with the CLI that might be relevant.

@adamziel adamziel force-pushed the use-blueprints-v2-runner-in-cli branch from f83ca60 to a44534d Compare July 1, 2025 10:09
@adamziel
Copy link
Collaborator Author

adamziel commented Jul 1, 2025

I've tested it on 110 Blueprints from the Blueprints and it worked. I'd like to merge this PR soon, perhaps even today (Wednesday), and start iterating on improvements. CC @fellyph – we'll need to adjust the Playground CLI README again and communicate this change on make.wp.org/playground, ideally with a bunch of context on Blueprints v2.

@adamziel
Copy link
Collaborator Author

adamziel commented Jul 3, 2025

The integration glue is ready for merging. However, I found 5 Blueprints where Playground is crashing in different ways. This PR may need to wait for a few more platform improvements around:

adamziel added a commit that referenced this pull request Jul 13, 2025
## Description

This PR adds supports for reading from non-blocking streams
(`stream_set_blocking($fp, true)`) by aligning the streaming logic with
unix kernel:

* `stream_set_blocking()` is now able to correctly mark the OS stream as
non blocking via `fcntl()`
* `fd_read()` either blocks or immediately returns based on stream type
* `js_open_process()`, `proc_open()` et al. now work directly with
kernel pipes. There's no more special event-based handling, polling, or
special casing for pumping stdin data to the process.
* PIPEFS no longer returns EWOULDBLOCK when the pipe is blocking, lacks
data, and the other side is already closed
* Removed special cases for polling processes, including
`PHPWASM.child_proc_by_fd`. We rely on kernel pipes now.
* Removed PHP 7.4 patch to treat blocking streams in a special way

### Motivation for this patch

With this PR, we can use the Symfony Process component. It spawns a new
process via proc_open(), marks its output stream as non-blocking, and
immediately reads the initial output bytes if any are present. Before
this change, Playground would block until the first output is produced,
and, sometimes until the entire process finishes.

This PR is a pre-requisite to #2281 

### Other changes

This PR implements `usleep()` (via `-Wl,--wrap=usleep`). It turns out
that it wasn't actually blocking the execution and a few unit tests
relying on it only passed due to a buggy implementation of blocking
streams.

### Follow-up work

* Replace `read()` globally with something equivalent to `wasm_read()`.
Right now we only do this `RUN /root/replace.sh 's/ret = read/ret =
wasm_read/g' /root/php-src/main/streams/plain_wrapper.c`
* Consider not overriding `php_pollfd_for` and removing
`wasm_poll_socket()` – wrapping select/poll/read syscalls may be enough
* We could remove more special casing, including socket polling inside
`wasm_poll_socket`, the `js_popen_to_file` function, `awaitData` et al.

## Testing Instructions (or ideally a Blueprint)

Tests are included so just confirm the CI is green.
adamziel added a commit that referenced this pull request Jul 14, 2025
Extracts `sandboxedSpawnHandlerFactory()` as a separate export from
`@php-wasm/universal`. Every module can reuse it now instead of
recreating new spawn handlers.

Pre-requisite for #2281 

 ## Testing instructions

Confirm the CI tests pass
adamziel added a commit that referenced this pull request Jul 14, 2025
## Motivation for the change, related issues

Simplifies the auto mounting logic and adds explicit semantics for
creating additional Blueprint steps as a result of an auto mount, e.g.
installing and activating a plugin that was automatically detected.
Also, adds unit tests for the automounting detection.

A part of #2281 

## Testing Instructions (or ideally a Blueprint)

Confirm the tests are still green
adamziel added a commit that referenced this pull request Jul 14, 2025
## Motivation for the change, related issues

Moves Blueprints v2 `.phar` file and the TypeScript module into the
`@wp-playground/cli` package and brushes up its logic.

This PR only moves dead code around. However, it also changes the
dependency graph of the project and affects the computed package.json
files, as in `@wp-playground/cli` now depends on `fs-ext`. For that
reason, there's also a change in the build machinery.

Also, `v2.spec.ts` uncovered a piping issue where polling after closing
the pipe caused a fatal error. Therefore, this PR adds an FS.isClosed()
check.

A part of #2281

## Testing plan

CI
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants