diff --git a/packages/playground/remote/src/lib/worker-thread.ts b/packages/playground/remote/src/lib/worker-thread.ts
index 100e45dcf4..0dbf0d31de 100644
--- a/packages/playground/remote/src/lib/worker-thread.ts
+++ b/packages/playground/remote/src/lib/worker-thread.ts
@@ -303,6 +303,10 @@ try {
// let's always fail.
php.setSpawnHandler(
createSpawnHandler(async function (args, processApi, options) {
+ if (args[0] === 'exec') {
+ args.shift();
+ }
+
// Mock programs required by wp-cli:
if (
args[0] === '/usr/bin/env' &&
@@ -323,6 +327,24 @@ try {
});
processApi.flushStdin();
processApi.exit(0);
+ } else if (args[0] === 'fetch') {
+ processApi.flushStdin();
+ fetch(args[1]).then(async (res) => {
+ const reader = res.body?.getReader();
+ if (!reader) {
+ processApi.exit(1);
+ return;
+ }
+ while (true) {
+ const { done, value } = await reader.read();
+ if (done) {
+ processApi.exit(0);
+ break;
+ }
+ processApi.stdout(value);
+ }
+ });
+ return;
} else if (args[0] === 'php') {
if (!childPHP) {
childPHP = new WebPHP(await recreateRuntime(), {
@@ -360,7 +382,7 @@ try {
$GLOBALS['argv'] = array_merge([
"/wordpress/wp-cli.phar",
"--path=/wordpress"
- ], ${phpVar(args.slice(1))});
+ ], ${phpVar(args.slice(2))});
// Provide stdin, stdout, stderr streams outside of
// the CLI SAPI.
@@ -379,10 +401,10 @@ try {
}`,
env: options.env,
});
- } else if (args[1].includes('wp-cli.phar')) {
+ } else if (args[1] === 'wp-cli.phar') {
result = await childPHP.run({
throwOnError: true,
- code: `${cliBootstrapScript} require( "/wordpress/wp-cli.phar" )`,
+ code: `${cliBootstrapScript} require( "/wordpress/wp-cli.phar" );`,
env: {
...options.env,
// Set SHELL_PIPE to 0 to ensure WP-CLI formats
@@ -395,6 +417,7 @@ try {
result = await childPHP.run({
throwOnError: true,
scriptPath: args[1],
+ env: options.env,
});
}
processApi.stdout(result.bytes);
diff --git a/packages/playground/website/demos/blueprints.phar b/packages/playground/website/demos/blueprints.phar
new file mode 100755
index 0000000000..7335950d12
Binary files /dev/null and b/packages/playground/website/demos/blueprints.phar differ
diff --git a/packages/playground/website/demos/php-blueprints.html b/packages/playground/website/demos/php-blueprints.html
new file mode 100644
index 0000000000..9724b568e1
--- /dev/null
+++ b/packages/playground/website/demos/php-blueprints.html
@@ -0,0 +1,30 @@
+
+
+ Blueprints PHP library
+
+
+
+
+
+ Blueprints PHP library
+
+ Uses the
+ PHP implementation of Blueprints
+ to set up a WordPress site. Open the devtools for full output.
+
+
+
+
+ PHP Blueprint execution result output – it may take a while to load
+
+
+
diff --git a/packages/playground/website/demos/php-blueprints.ts b/packages/playground/website/demos/php-blueprints.ts
new file mode 100644
index 0000000000..04aa3d0a41
--- /dev/null
+++ b/packages/playground/website/demos/php-blueprints.ts
@@ -0,0 +1,125 @@
+import { startPlaygroundWeb } from '@wp-playground/client';
+import { getRemoteUrl } from '../src/lib/config';
+import { joinPaths } from '@php-wasm/util';
+export {};
+
+const iframe = document.querySelector('iframe')!;
+const playground = await startPlaygroundWeb({
+ iframe,
+ remoteUrl: getRemoteUrl().toString(),
+ // Blueprint v1, implemented in TypeScript:
+ blueprint: {
+ preferredVersions: {
+ wp: 'latest',
+ // Required for the PHP library to run:
+ php: '8.2',
+ },
+ features: {
+ networking: true,
+ },
+ // landingPage: '/wp-content/index.php',
+ landingPage: '/',
+ // Required for the PHP library to run:
+ phpExtensionBundles: ['kitchen-sink'],
+ },
+});
+
+const response = await fetch('./blueprints.phar');
+const phar = new Uint8Array(await response.arrayBuffer());
+await playground.writeFile(
+ joinPaths(await playground.documentRoot, 'blueprints.phar'),
+ phar
+);
+const outputDiv = document.getElementById('output')!;
+
+try {
+ const wpCliRequest = fetch(
+ 'https://raw.githubusercontent.com/wp-cli/builds/gh-pages/phar/wp-cli.phar'
+ );
+ const wpCliResponse = await wpCliRequest;
+ const wpCli = await wpCliResponse.arrayBuffer();
+ await playground.writeFile('/wordpress/wp-cli.phar', new Uint8Array(wpCli));
+
+ // Blueprint v2, implemented in PHP. The PHP builder is not required. It only
+ // produces a JSON document that is then used to run the Blueprint.
+ const result = await playground.run({
+ code: ` "check-requirements": false,
+ * Then requiring it breaks http and https requests:
+ *
+ * > echo file_get_contents('http://localhost:5400/website-server/');
+ * > Warning: PHP Request Startup: Failed to open stream: Operation timed out in php-wasm run script on line 13
+ *
+ * The check is therefore disabled for now.
+ */
+ require '/wordpress/blueprints.phar';
+
+ $blueprint = BlueprintBuilder::create()
+ // This isn't a WordPress zip file since wordpress.org
+ // doesn't expose the right CORS headers. It is a HTTPS-hosted
+ // zip file nonetheless, and we can use it for testing.
+ // Uncomment this as needed
+ // ->setWordPressVersion( 'https://downloads.wordpress.org/plugin/hello-dolly.1.7.3.zip' )
+
+ ->withFile( 'wordpress.txt', (new UrlResource())->setUrl('https://downloads.wordpress.org/plugin/hello-dolly.zip') )
+ ->withSiteOptions( [
+ 'blogname' => 'My Playground Blog',
+ ] )
+ ->withWpConfigConstants( [
+ 'WP_DEBUG' => true,
+ 'WP_DEBUG_LOG' => true,
+ 'WP_DEBUG_DISPLAY' => true,
+ 'WP_CACHE' => true,
+ ] )
+ ->withPlugins( [
+ 'https://downloads.wordpress.org/plugin/hello-dolly.zip',
+ // When the regular UrlDataSource is used, the second
+ // downloaded zip file always errors with:
+ // > Failed to open stream: Operation timed out
+ 'https://downloads.wordpress.org/plugin/classic-editor.zip',
+ 'https://downloads.wordpress.org/plugin/gutenberg.17.7.0.zip',
+ ] )
+ ->withTheme( 'https://downloads.wordpress.org/theme/pendant.zip' )
+ ->withContent( 'https://raw.githubusercontent.com/WordPress/theme-test-data/master/themeunittestdata.wordpress.xml' )
+ ->andRunSQL( <<<'SQL'
+ CREATE TABLE tmp_table ( id INT );
+ INSERT INTO tmp_table VALUES (1);
+ INSERT INTO tmp_table VALUES (2);
+ SQL
+ )
+ ->withFile( 'wordpress.txt', 'Data' )
+ ->toBlueprint()
+ ;
+
+ echo "Running the following Blueprint:\n";
+ echo json_encode($blueprint, JSON_PRETTY_PRINT)."\n\n";
+ $results = run_blueprint( $blueprint, '/wordpress' );
+ echo "Blueprint execution finished!\n";
+ echo "Contents of /wordpress/wp-content/plugins:";
+ print_r(glob('/wordpress/wp-content/plugins/*'));
+ `,
+ throwOnError: true,
+ });
+
+ outputDiv.textContent = result.text;
+ console.log(result.text);
+} catch (e) {
+ console.error(e);
+ outputDiv.textContent = e + '';
+ throw e;
+}
+
+console.log(await playground.listFiles('/wordpress/wp-content/plugins'));
diff --git a/packages/playground/website/demos/wordpress.zip b/packages/playground/website/demos/wordpress.zip
new file mode 100644
index 0000000000..54825cf516
Binary files /dev/null and b/packages/playground/website/demos/wordpress.zip differ
diff --git a/packages/playground/website/tsconfig.app.json b/packages/playground/website/tsconfig.app.json
index 9b9b776920..2edc05ecaf 100644
--- a/packages/playground/website/tsconfig.app.json
+++ b/packages/playground/website/tsconfig.app.json
@@ -29,6 +29,7 @@
"demos/peer.ts",
"demos/terminal.ts",
"demos/terminal-component.tsx",
+ "demos/php-blueprints.ts",
"./cypress.config.ts"
]
}