Skip to content

Commit

Permalink
Playground CLI tool (#1289)
Browse files Browse the repository at this point in the history
A CLI tool for running WordPress playground locally instead of in the
browser.

## Usage when testing this PR

```shell
# Just run a clean-slate WordPress server
$ bun packages/playground/cli/src/cli.ts server --wp=6.5
WordPress is running on http://127.0.0.1:9400

# Start PHP server and mount a WordPress develop build at /wordpress
$ bun ./packages/playground/cli/src/cli.ts server --mount=../../wordpress-develop/build:/wordpress
WordPress is running on http://127.0.0.1:9400
```

The end goal is to create a portable cross-OS server binary with Bun,
see #1056 for
more context.

## Usage after merging this PR

```shell
$ npx @wp-playground/cli server --wp=6.5
```

## Flexible and unoppinionated

Playground CLI is simple, configurable, and unoppinionated. You can set
it up
to your unique WordPress setup. For example, this command would run the
documentation
workflow at https://github.com/adamziel/playground-docs-workflow:

```shell
bun --config=/Users/adam/.bunfig.toml \
    ./packages/playground/cli/src/cli.ts \
    server \
    --mount=./wp-content/plugins/wp-docs-plugin:/wordpress/wp-content/plugins/wp-docs-plugin \
    --mount=./wp-content/html-pages:/wordpress/wp-content/html-pages \
    --mount=./wp-content/uploads:/wordpress/wp-content/uploads \
    --mount=./wp-content/themes/playground-docs:/wordpress/wp-content/themes/playground-docs \
    --blueprint=./wp-content/blueprint-wp-now.json \
    --wp=6.5
```

It is long, sure, but it is also very flexible. If you need a shorter
version, you can alias
it or write a bash script. In the future, Blueprints might support
relative path mappings,
at which point that command would get much shorter.

## Usage 

Playground CLI supports three commands:

-   `server` - start a fresh WordPress playground server.
- `build-snapshot` – run a Blueprint and output a .zip file with the
resulting WordPress instance.
-   `run-blueprint` – just run the Blueprint and exit

Here's the CLI usage:

```
Positionals:
  command  Command to run                   [string] [choices: "server", "run-blueprint", "build-snapshot"]

Options:
  --help                Show help                                      [boolean]
  --version             Show version number                            [boolean]
  --outfile             When building, write to this output file.
                                             [string] [default: "wordpress.zip"]
  --port                Port to listen on when serving. [number] [default: 9400]
  --php                 PHP version to use.
  [string] [choices: "8.3", "8.2", "8.1", "8.0", "7.4", "7.3", "7.2", "7.1", "7.
                                                            0"] [default: "8.0"]
  --wp                  WordPress version to use.   [string] [default: "latest"]
  --mount               Mount a directory to the PHP runtime.            [array]
  --login               Should log the user in        [boolean] [default: false]
  --blueprint           Blueprint to execute.                           [string]
  --skipWordPressSetup  Do not download, unzip, and install WordPress. Useful for
                        mounting a pre-configured WordPress directory at /wordpress
                        [boolean] [default: false]
```

### Server

Playground is a WordPress server:

```shell
$ bun ./packages/playground/cli/src/cli.ts server
Setting up WordPress latest
Running a blueprint – 100%
WordPress is running on http://127.0.0.1:9400
```

### Builder

Playground can build a `Blueprint.json` file into a `wordpress.zip`:

```shell
$ bun ./packages/playground/cli/src/cli.ts build \
     --blueprint=./blueprint.json \
     --outfile=wp.zip
Setting up WordPress latest
Running the blueprint – 100%
Zipping WordPress to wp.zip
WordPress saved to wp.zip
```

All the mounts you specify will still apply.

## Philosophy

The data flow is as follows:

-   Start PHP
-   Mount any local directories
- Put a fresh WordPress in the resulting virtual filesystem (unless
you're mounting directly at /wordpress).
-   Start a local server, accept requests
-   Run the Blueprint

On each run, a fresh WordPress release is unzipped in the virtual
filesystem. It is sourced
from a zip file cached at `~/.wordpress-playground/`. If you mess up
your site, just restart the
server and you'll get a fresh one, again unzipped. The CLI tool never
modifies the zip file
so you can always be sure you're starting from a clean slate.

## Future work

In the guture, Playground CLI may support:

-   Loading Blueprints from URLs.
-   Saving the running WordPress site and loading it later.
- Caching all remote resources referenced in Blueprints. Currently, they
are downloaded on each run.

Conceptually, this isn't too different from Docker containers. There are
images (zip files),
containers (running instances), and commands (Blueprints). Playground
could support the same
concepts such as:

-   Listing and managing available images and containers.
-   Saving a running container and restoring it later
- Starting a container from a specific image (already supported via zip
files)
-   Running a command in a container (the `php` command)
-   Building a new image from a Blueprint (the `build` command)
- Step-by-step cache for Blueprints so that only the changed steps are
re-run.

## Interoperability

This CLI package is not just a useful tool. It drives interoperability
between the in-browser
Playground, CLI packages, and the PHP Blueprints library. Once complete,
it will reuse the
same internals as the website at https://playground.wordpress.org
whether we're talking about
running PHP code, executing Blueprints, building snapshots, serving
requests, or maintaining
multiple PHP instances

## Known issues

* The progress bar sometimes does not reach 100% and the sequencing of
the events means a newline is displayed between `Running a blueprint`
and `100%`.

## Possible improvements

* Blueprints downloads are not cached but performed each time, e.g.
gutenberg.zip will be download on each build.
* The built binary is 180MB large as it bundles all the .wasm modules.
We could only ship PHP 8.0 for starters and download the rest on demand.
* Grid build for all the OSes and CPUs.

## How do I run the binary? I'm getting an error!

@stoph Wrote this excellent guide:

Once downloaded find the file in your terminal. If you initially try to
run it, you’ll get an error

```shell
$ ./playground-cli
zsh: permission denied: ./playground-cli
```

You’ll need to grant execute permissions to allow it to run

```shell
$ chmod +x playground-cli
```

You’ll then get a pop-up form MacOS warning about untrusted code. 


![unnamed](https://github.com/WordPress/wordpress-playground/assets/205419/09e03278-d83f-44be-8417-fed9f6e0a244)

Find the file in your Finder. Right click and select “Open” This should
not give you the option to Open from the dialog.


![unnamed](https://github.com/WordPress/wordpress-playground/assets/205419/367cb3dc-9883-4de0-bc66-99e0b4804f9b)

Once that’s done, you can close the terminal it launched and should be
able to run it without issue from the command line going forward.

```shell
$ ./playground-cli server
```

cc @dmsnell
  • Loading branch information
adamziel authored Apr 29, 2024
1 parent 226fb92 commit 7c229a6
Show file tree
Hide file tree
Showing 58 changed files with 1,383 additions and 148 deletions.
1 change: 1 addition & 0 deletions bunfig.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
preload = ["./packages/bun-extensions/src/import-files-as-text.ts"]
2 changes: 1 addition & 1 deletion lerna.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"$schema": "node_modules/lerna/schemas/lerna-schema.json",
"useWorkspaces": true,
"version": "0.7.1",
"version": "0.7.2",
"useNx": true
}
44 changes: 26 additions & 18 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

33 changes: 33 additions & 0 deletions packages/bun-extensions/src/import-files-as-text.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { plugin, BunPlugin } from 'bun';

/**
* Adds support for `import contents from "file.php?raw"`.
*
* It matches Vite's behavior, which is to read the file as text
* and return its textual content. This allows us to run Bun and
* Vite on the same codebase.
*/
export const ImportFilesAsTextPlugin: BunPlugin = {
name: 'ImportFilesAsTextPlugin',
setup(build) {
build.onLoad(
{
// Looks for paths with a `raw` query parameter.
filter: /[?&]raw(?:[&=]|$)/,
},
async (args) => {
const path = args.path.split('?')[0];
// Use Bun.file to read the .php file as text
const text = await Bun.file(path).text();

// Return the file content as a module exporting the text
return {
contents: `export default ${JSON.stringify(text)};`,
loader: 'js', // Treat the content as JavaScript
};
}
);
},
};

plugin(ImportFilesAsTextPlugin);
2 changes: 1 addition & 1 deletion packages/php-wasm/cli/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@php-wasm/cli",
"version": "0.7.1",
"version": "0.7.2",
"description": "PHP.wasm CLI for node.js",
"repository": {
"type": "git",
Expand Down
6 changes: 1 addition & 5 deletions packages/php-wasm/compile/php/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -1024,11 +1024,7 @@ RUN set -euxo pipefail; \
# Turn the php.js file into an ES module
# Manually turn the output into a esm module instead of relying on -s MODULARIZE=1.
# which pollutes the global namespace and does not play well with import() mechanics.
if [ "$EMSCRIPTEN_ENVIRONMENT" = "node" ]; then \
echo "const dependencyFilename = __dirname + '/${PHP_VERSION_ESCAPED}/$WASM_FILENAME'; " >> /root/output/php-module.js; \
else \
echo "import dependencyFilename from './${PHP_VERSION_ESCAPED}/$WASM_FILENAME'; " >> /root/output/php-module.js; \
fi; \
echo "import dependencyFilename from './${PHP_VERSION_ESCAPED}/$WASM_FILENAME'; " >> /root/output/php-module.js; \
echo "export { dependencyFilename }; " >> /root/output/php-module.js && \
echo "export const dependenciesTotalSize = $FILE_SIZE; " >> /root/output/php-module.js && \
cat /root/esm-prefix.js >> /root/output/php-module.js && \
Expand Down
2 changes: 1 addition & 1 deletion packages/php-wasm/fs-journal/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@php-wasm/fs-journal",
"version": "0.7.1",
"version": "0.7.2",
"description": "Bindings to journal the PHP filesystem",
"repository": {
"type": "git",
Expand Down
5 changes: 5 additions & 0 deletions packages/php-wasm/fs-journal/vite.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@ import { join } from 'path';
// eslint-disable-next-line @nx/enforce-module-boundaries
import { viteTsConfigPaths } from '../../vite-ts-config-paths';

// eslint-disable-next-line @nx/enforce-module-boundaries
import { viteWasmLoader } from '../../vite-wasm-loader';

export default defineConfig({
cacheDir: '../../../node_modules/.vite/php-wasm-fs-journal',

Expand All @@ -19,6 +22,8 @@ export default defineConfig({
viteTsConfigPaths({
root: '../../../',
}),

viteWasmLoader,
],

// Configuration for building your library.
Expand Down
5 changes: 3 additions & 2 deletions packages/php-wasm/logger/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@php-wasm/logger",
"version": "0.7.1",
"version": "0.7.2",
"description": "A logger for PHP-wasm clients like Playground and WP-now.",
"repository": {
"type": "git",
Expand All @@ -19,7 +19,8 @@
"directory": "../../../dist/packages/php-wasm/logger"
},
"license": "GPL-2.0-or-later",
"main": "index.cjs",
"type": "module",
"main": "index.js",
"types": "index.d.ts",
"engines": {
"node": ">=18.18.0",
Expand Down
2 changes: 1 addition & 1 deletion packages/php-wasm/node-polyfills/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@php-wasm/node-polyfills",
"version": "0.7.1",
"version": "0.7.2",
"description": "PHP.wasm – polyfills for Node.js",
"repository": {
"type": "git",
Expand Down
37 changes: 37 additions & 0 deletions packages/php-wasm/node/bin/postprocess-php-modules.cjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
/**
* Replaces `import dependencyFilename from ` with `const dependencyFilename = __dirName + `
* in all files matching glob `php_*.js` in directory specified by argv[0].
*
* We need this to:
*
* * Produce a CommonJS and ESM-compliant module using Vite
* * Make the uncompiled source compatible with Bun
*/
const fs = require('fs');
const glob = require('glob');

const directory = process.argv[2];

const files = glob.globSync(`${directory}/php_*.js`);
files.forEach((file) => {
fs.readFile(file, 'utf8', (err, data) => {
if (err) {
console.error(err);
return;
}

const updatedData = data.replace(
'import dependencyFilename from ',
'const dependencyFilename = __dirname + '
);

fs.writeFile(file, updatedData, 'utf8', (err) => {
if (err) {
console.error(err);
return;
}

console.log(`Updated file: ${file}`);
});
});
});
2 changes: 1 addition & 1 deletion packages/php-wasm/node/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@php-wasm/node",
"version": "0.7.1",
"version": "0.7.2",
"description": "PHP.wasm for Node.js",
"repository": {
"type": "git",
Expand Down
4 changes: 3 additions & 1 deletion packages/php-wasm/node/project.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,9 @@
"executor": "nx:run-commands",
"options": {
"commands": [
"cp -rf packages/php-wasm/node/public/* dist/packages/php-wasm/node"
"cp -rf packages/php-wasm/node/public/* dist/packages/php-wasm/node",
"node packages/php-wasm/node/bin/postprocess-php-modules.cjs dist/packages/php-wasm/node",
"rm -rf dist/packages/php-wasm/node/*.wasm"
],
"parallel": false
},
Expand Down
2 changes: 1 addition & 1 deletion packages/php-wasm/node/public/php_7_0.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
const dependencyFilename = __dirname + '/7_0_33/php_7_0.wasm';
import dependencyFilename from './7_0_33/php_7_0.wasm';
export { dependencyFilename };
export const dependenciesTotalSize = 13118939;
export function init(RuntimeName, PHPLoader) {
Expand Down
2 changes: 1 addition & 1 deletion packages/php-wasm/node/public/php_7_1.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
const dependencyFilename = __dirname + '/7_1_30/php_7_1.wasm';
import dependencyFilename from './7_1_30/php_7_1.wasm';
export { dependencyFilename };
export const dependenciesTotalSize = 13643181;
export function init(RuntimeName, PHPLoader) {
Expand Down
2 changes: 1 addition & 1 deletion packages/php-wasm/node/public/php_7_2.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
const dependencyFilename = __dirname + '/7_2_34/php_7_2.wasm';
import dependencyFilename from './7_2_34/php_7_2.wasm';
export { dependencyFilename };
export const dependenciesTotalSize = 14335706;
export function init(RuntimeName, PHPLoader) {
Expand Down
2 changes: 1 addition & 1 deletion packages/php-wasm/node/public/php_7_3.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
const dependencyFilename = __dirname + '/7_3_33/php_7_3.wasm';
import dependencyFilename from './7_3_33/php_7_3.wasm';
export { dependencyFilename };
export const dependenciesTotalSize = 14441796;
export function init(RuntimeName, PHPLoader) {
Expand Down
2 changes: 1 addition & 1 deletion packages/php-wasm/node/public/php_7_4.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
const dependencyFilename = __dirname + '/7_4_33/php_7_4.wasm';
import dependencyFilename from './7_4_33/php_7_4.wasm';
export { dependencyFilename };
export const dependenciesTotalSize = 14675209;
export function init(RuntimeName, PHPLoader) {
Expand Down
2 changes: 1 addition & 1 deletion packages/php-wasm/node/public/php_8_0.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
const dependencyFilename = __dirname + '/8_0_30/php_8_0.wasm';
import dependencyFilename from './8_0_30/php_8_0.wasm';
export { dependencyFilename };
export const dependenciesTotalSize = 13904218;
export function init(RuntimeName, PHPLoader) {
Expand Down
2 changes: 1 addition & 1 deletion packages/php-wasm/node/public/php_8_1.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
const dependencyFilename = __dirname + '/8_1_23/php_8_1.wasm';
import dependencyFilename from './8_1_23/php_8_1.wasm';
export { dependencyFilename };
export const dependenciesTotalSize = 13884035;
export function init(RuntimeName, PHPLoader) {
Expand Down
2 changes: 1 addition & 1 deletion packages/php-wasm/node/public/php_8_2.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
const dependencyFilename = __dirname + '/8_2_10/php_8_2.wasm';
import dependencyFilename from './8_2_10/php_8_2.wasm';
export { dependencyFilename };
export const dependenciesTotalSize = 14143340;
export function init(RuntimeName, PHPLoader) {
Expand Down
2 changes: 1 addition & 1 deletion packages/php-wasm/node/public/php_8_3.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
const dependencyFilename = __dirname + '/8_3_0/php_8_3.wasm';
import dependencyFilename from './8_3_0/php_8_3.wasm';
export { dependencyFilename };
export const dependenciesTotalSize = 14523141;
export function init(RuntimeName, PHPLoader) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,7 @@ export function initOutboundWebsocketProxyServer(

// Handle new WebSocket client
async function onWsConnect(client: any, request: http.IncomingMessage) {
const clientAddr = client._socket.remoteAddress;
const clientAddr = client?._socket?.remoteAddress || client.url;
const clientLog = function (...args: any[]) {
log(' ' + clientAddr + ': ', ...args);
};
Expand Down
7 changes: 6 additions & 1 deletion packages/php-wasm/node/src/lib/node-php.ts
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,12 @@ export class NodePHP extends BasePHP {
*/
@rethrowFileSystemError('Could not mount {path}')
mount(localPath: string | MountSettings, virtualFSPath: string) {
if (!this.fileExists(virtualFSPath)) {
const localRoot =
typeof localPath === 'object' ? localPath.root : localPath;
if (
!this.fileExists(virtualFSPath) &&
lstatSync(localRoot).isDirectory()
) {
this.mkdirTree(virtualFSPath);
}
this[__private__dont__use].FS.mount(
Expand Down
Loading

0 comments on commit 7c229a6

Please sign in to comment.