From 815acec79a2312e1f576d5f428974760727b6622 Mon Sep 17 00:00:00 2001 From: eliot-akira Date: Wed, 24 May 2023 02:35:57 +0200 Subject: [PATCH 01/43] Rough draft for installPlugin --- .../src/lib/steps/install-plugin.spec.ts | 49 ++++++++++++++++ .../src/lib/steps/install-plugin.ts | 56 +++++++++---------- 2 files changed, 74 insertions(+), 31 deletions(-) create mode 100644 packages/playground/blueprints/src/lib/steps/install-plugin.spec.ts diff --git a/packages/playground/blueprints/src/lib/steps/install-plugin.spec.ts b/packages/playground/blueprints/src/lib/steps/install-plugin.spec.ts new file mode 100644 index 0000000000..cf25541049 --- /dev/null +++ b/packages/playground/blueprints/src/lib/steps/install-plugin.spec.ts @@ -0,0 +1,49 @@ +import { NodePHP } from '@php-wasm/node'; +import { compileBlueprint, runBlueprintSteps } from '../compile'; + +const phpVersion = '8.0'; +describe('Blueprint step installPlugin', () => { + let php: NodePHP; + beforeEach(async () => { + php = await NodePHP.load(phpVersion, { + requestHandler: { + documentRoot: '/', + isStaticFilePath: (path) => !path.endsWith('.php'), + }, + }); + }); + + it('should install a plugin', async () => { + + // Create test plugin + + php.mkdir('/test-plugin') + php.writeFile('/test-plugin/index.php', `/**\n * Plugin Name: Test Plugin`); + + await php.run({ + code: `open("test-plugin.zip", ZIPARCHIVE::CREATE); $zip->addFile("/test-plugin/index.php"); $zip->close();` + }) + + php.rmdir('/test-plugin') + + expect(php.fileExists('/test-plugin.zip')).toBe(true); + + await runBlueprintSteps( + compileBlueprint({ + steps: [ + { + step: 'installPlugin', + pluginZipFile: { + resource: 'vfs', + path: '/test-plugin.zip', + }, + }, + ], + }), + php + ); + + expect(php.fileExists(`${php.documentRoot}/wp-content/test-plugin`)).toBe(true); + + }, 30000); +}); diff --git a/packages/playground/blueprints/src/lib/steps/install-plugin.ts b/packages/playground/blueprints/src/lib/steps/install-plugin.ts index 2da309da57..6eff97a04e 100644 --- a/packages/playground/blueprints/src/lib/steps/install-plugin.ts +++ b/packages/playground/blueprints/src/lib/steps/install-plugin.ts @@ -1,6 +1,8 @@ import { UniversalPHP } from '@php-wasm/universal'; import { StepHandler } from '.'; -import { asDOM, zipNameToHumanName } from './common'; +import { zipNameToHumanName } from './common'; +import { writeFile } from './client-methods' +import { activatePlugin } from './activate-plugin' /** * @inheritDoc installPlugin @@ -64,39 +66,31 @@ export const installPlugin: StepHandler> = async ( try { const activate = 'activate' in options ? options.activate : true; - // Upload it to WordPress - const pluginForm = await playground.request({ - url: '/wp-admin/plugin-install.php?tab=upload', - }); - const pluginFormPage = asDOM(pluginForm); - const pluginFormData = new FormData( - pluginFormPage.querySelector('.wp-upload-form')! as HTMLFormElement - ) as any; - // eslint-disable-next-line @typescript-eslint/no-unused-vars - const { pluginzip, ...postData } = Object.fromEntries( - pluginFormData.entries() - ); + const pluginsPath = `${playground.documentRoot}/wp-content/plugins` + const pluginZipPath = `${pluginsPath}/${pluginZipFile.name}` + + await writeFile(playground, { + path: pluginZipPath, + data: pluginZipFile + }) - const pluginInstalledResponse = await playground.request({ - url: '/wp-admin/update.php?action=upload-plugin', - method: 'POST', - formData: postData, - files: { pluginzip: pluginZipFile }, + await playground.run({ + code: `open('${pluginZipPath}'); +if ($res) { + $zip->extractTo('${pluginsPath}'); + $zip->close(); +} +`, }); - // Activate if needed + const pluginPath = pluginZipPath.replace('.zip', '') + if (activate) { - const pluginInstalledPage = asDOM(pluginInstalledResponse); - const activateButtonHref = pluginInstalledPage - .querySelector('#wpbody-content .button.button-primary')! - .attributes.getNamedItem('href')!.value; - const activatePluginUrl = new URL( - activateButtonHref, - await playground.pathToInternalUrl('/wp-admin/') - ).toString(); - await playground.request({ - url: activatePluginUrl, - }); + await activatePlugin(playground, { + pluginPath + }, progress) } /** @@ -155,7 +149,7 @@ export const installPlugin: StepHandler> = async ( } } catch (error) { console.error( - `Proceeding without the ${pluginZipFile.name} theme. Could not install it in wp-admin. ` + + `Proceeding without the ${pluginZipFile.name} plugin. Could not install it in wp-admin. ` + `The original error was: ${error}` ); console.error(error); From 61e33f3182c6d1c63d70e09973f6791ca53c2bad Mon Sep 17 00:00:00 2001 From: eliot-akira Date: Wed, 24 May 2023 02:38:15 +0200 Subject: [PATCH 02/43] Format --- .../src/lib/steps/install-plugin.spec.ts | 19 ++++++++------- .../src/lib/steps/install-plugin.ts | 24 +++++++++++-------- 2 files changed, 25 insertions(+), 18 deletions(-) diff --git a/packages/playground/blueprints/src/lib/steps/install-plugin.spec.ts b/packages/playground/blueprints/src/lib/steps/install-plugin.spec.ts index cf25541049..1d42f10305 100644 --- a/packages/playground/blueprints/src/lib/steps/install-plugin.spec.ts +++ b/packages/playground/blueprints/src/lib/steps/install-plugin.spec.ts @@ -14,17 +14,19 @@ describe('Blueprint step installPlugin', () => { }); it('should install a plugin', async () => { - // Create test plugin - php.mkdir('/test-plugin') - php.writeFile('/test-plugin/index.php', `/**\n * Plugin Name: Test Plugin`); + php.mkdir('/test-plugin'); + php.writeFile( + '/test-plugin/index.php', + `/**\n * Plugin Name: Test Plugin` + ); await php.run({ - code: `open("test-plugin.zip", ZIPARCHIVE::CREATE); $zip->addFile("/test-plugin/index.php"); $zip->close();` - }) + code: `open("test-plugin.zip", ZIPARCHIVE::CREATE); $zip->addFile("/test-plugin/index.php"); $zip->close();`, + }); - php.rmdir('/test-plugin') + php.rmdir('/test-plugin'); expect(php.fileExists('/test-plugin.zip')).toBe(true); @@ -43,7 +45,8 @@ describe('Blueprint step installPlugin', () => { php ); - expect(php.fileExists(`${php.documentRoot}/wp-content/test-plugin`)).toBe(true); - + expect( + php.fileExists(`${php.documentRoot}/wp-content/test-plugin`) + ).toBe(true); }, 30000); }); diff --git a/packages/playground/blueprints/src/lib/steps/install-plugin.ts b/packages/playground/blueprints/src/lib/steps/install-plugin.ts index 6eff97a04e..110713c922 100644 --- a/packages/playground/blueprints/src/lib/steps/install-plugin.ts +++ b/packages/playground/blueprints/src/lib/steps/install-plugin.ts @@ -1,8 +1,8 @@ import { UniversalPHP } from '@php-wasm/universal'; import { StepHandler } from '.'; import { zipNameToHumanName } from './common'; -import { writeFile } from './client-methods' -import { activatePlugin } from './activate-plugin' +import { writeFile } from './client-methods'; +import { activatePlugin } from './activate-plugin'; /** * @inheritDoc installPlugin @@ -66,13 +66,13 @@ export const installPlugin: StepHandler> = async ( try { const activate = 'activate' in options ? options.activate : true; - const pluginsPath = `${playground.documentRoot}/wp-content/plugins` - const pluginZipPath = `${pluginsPath}/${pluginZipFile.name}` + const pluginsPath = `${playground.documentRoot}/wp-content/plugins`; + const pluginZipPath = `${pluginsPath}/${pluginZipFile.name}`; await writeFile(playground, { path: pluginZipPath, - data: pluginZipFile - }) + data: pluginZipFile, + }); await playground.run({ code: ` Date: Wed, 24 May 2023 21:40:20 +0200 Subject: [PATCH 03/43] Merge remote-tracking branch 'upstream/trunk' --- .editorconfig | 2 ++ README.md | 2 +- .../docs/13-contributing/05-publishing.md | 2 +- packages/wp-now/README.md | 2 +- packages/wp-now/src/tests/wp-now.spec.ts | 22 ++++++++++--------- packages/wp-now/src/wp-now.ts | 8 ++++--- 6 files changed, 22 insertions(+), 16 deletions(-) diff --git a/.editorconfig b/.editorconfig index 82d9dadf72..0d53770931 100644 --- a/.editorconfig +++ b/.editorconfig @@ -5,6 +5,8 @@ root = true charset = utf-8 insert_final_newline = true trim_trailing_whitespace = true +indent_style = tab +indent_size = 4 [*.md] max_line_length = off diff --git a/README.md b/README.md index 70149ca45f..c6387bfde7 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# WordPress Playground and PHP WASM (WebAssembly) +# WordPress Playground and PHP WASM (WebAssembly) [Project Page](https://developer.wordpress.org/playground/) | [Live demo](https://playground.wordpress.net/) | [Documentation and API Reference](https://wordpress.github.io/wordpress-playground/) diff --git a/packages/docs/site/docs/13-contributing/05-publishing.md b/packages/docs/site/docs/13-contributing/05-publishing.md index 624d44e39d..8b6bc6c4c1 100644 --- a/packages/docs/site/docs/13-contributing/05-publishing.md +++ b/packages/docs/site/docs/13-contributing/05-publishing.md @@ -36,7 +36,7 @@ npm run release Internet connections drop, APIs stop responding, and GitHub rules are nasty. Stuff happens. If the publishing process fails, you may need to bump the version again and force a publish. To do so, execute the following command: ```bash -npm run release --force-publish +npm run release -- --force-publish ``` ## GitHub documentation diff --git a/packages/wp-now/README.md b/packages/wp-now/README.md index 6a910981e3..8d8dbd86f9 100644 --- a/packages/wp-now/README.md +++ b/packages/wp-now/README.md @@ -75,7 +75,7 @@ Of these, `wp-now php` currently supports the `--path=`, `--php=` If you are migrating from Laravel Valet, you should be aware of the differences it has with `wp-now`: -- `wp-now` does not require you to install WordPress separately, create a database, connect WordPress to that database or create a user account. All of these steps are handled by the `wp now start` command and are running under the hood; +- `wp-now` does not require you to install WordPress separately, create a database, connect WordPress to that database or create a user account. All of these steps are handled by the `wp-now start` command and are running under the hood; - `wp-now` works across all platforms (Mac, Linux, Windows); - `wp-now` does not support custom domains or SSL (yet!); - `wp-now` works with WordPress themes and plugins even if you don't have WordPress installed; diff --git a/packages/wp-now/src/tests/wp-now.spec.ts b/packages/wp-now/src/tests/wp-now.spec.ts index 36a6a7192b..9e060b2ef1 100644 --- a/packages/wp-now/src/tests/wp-now.spec.ts +++ b/packages/wp-now/src/tests/wp-now.spec.ts @@ -19,6 +19,14 @@ import getWpNowTmpPath from '../get-wp-now-tmp-path'; const exampleDir = __dirname + '/mode-examples'; +async function downloadWithTimer(name, fn) { + console.log(`Downloading ${name}...`); + console.time(name); + await fn(); + console.log(`${name} downloaded.`); + console.timeEnd(name); +} + // Options test('getWpNowConfig with default options', async () => { const rawOptions: CliOptions = { @@ -176,16 +184,10 @@ describe('Test starting different modes', () => { */ beforeAll(async () => { fs.rmSync(getWpNowTmpPath(), { recursive: true, force: true }); - console.log('Downloading WordPress...'); - console.time('wordpress'); - await downloadWordPress(); - console.log('WordPress downloaded.'); - console.timeEnd('wordpress'); - console.log('Downloading SQLite...'); - console.time('sqlite'); - await downloadSqliteIntegrationPlugin(); - console.log('SQLite downloaded.'); - console.timeEnd('sqlite'); + await Promise.all([ + downloadWithTimer('wordpresss', downloadWordPress), + downloadWithTimer('sqlite', downloadSqliteIntegrationPlugin), + ]); }); /** diff --git a/packages/wp-now/src/wp-now.ts b/packages/wp-now/src/wp-now.ts index fb696a56c8..b58296fe0d 100644 --- a/packages/wp-now/src/wp-now.ts +++ b/packages/wp-now/src/wp-now.ts @@ -88,9 +88,11 @@ export default async function startWPNow( }); return { php, phpInstances, options }; } - await downloadWordPress(options.wordPressVersion); - await downloadSqliteIntegrationPlugin(); - await downloadMuPlugins(); + await Promise.all([ + downloadWordPress(options.wordPressVersion), + downloadSqliteIntegrationPlugin(), + downloadMuPlugins(), + ]); const isFirstTimeProject = !fs.existsSync(options.wpContentPath); await applyToInstances(phpInstances, async (_php) => { switch (options.mode) { From 304e25618785f238057d7659da9b3c538267336d Mon Sep 17 00:00:00 2001 From: eliot-akira Date: Wed, 24 May 2023 21:41:58 +0200 Subject: [PATCH 04/43] Test zip package name different from extracted plugin folder name --- .../blueprints/src/lib/steps/install-plugin.spec.ts | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/packages/playground/blueprints/src/lib/steps/install-plugin.spec.ts b/packages/playground/blueprints/src/lib/steps/install-plugin.spec.ts index 1d42f10305..71ea16845a 100644 --- a/packages/playground/blueprints/src/lib/steps/install-plugin.spec.ts +++ b/packages/playground/blueprints/src/lib/steps/install-plugin.spec.ts @@ -16,19 +16,20 @@ describe('Blueprint step installPlugin', () => { it('should install a plugin', async () => { // Create test plugin - php.mkdir('/test-plugin'); + php.mkdir('/tmp/test-plugin'); php.writeFile( - '/test-plugin/index.php', + '/tmp/test-plugin/index.php', `/**\n * Plugin Name: Test Plugin` ); await php.run({ - code: `open("test-plugin.zip", ZIPARCHIVE::CREATE); $zip->addFile("/test-plugin/index.php"); $zip->close();`, + code: `open("test-plugin-0.0.1.zip", ZIPARCHIVE::CREATE); $zip->addFile("/tmp/test-plugin/index.php"); $zip->close();`, }); - php.rmdir('/test-plugin'); + php.rmdir('/tmp/test-plugin'); - expect(php.fileExists('/test-plugin.zip')).toBe(true); + // Note the package name is different from plugin folder name + expect(php.fileExists('/test-plugin-0.0.1.zip')).toBe(true); await runBlueprintSteps( compileBlueprint({ @@ -37,7 +38,7 @@ describe('Blueprint step installPlugin', () => { step: 'installPlugin', pluginZipFile: { resource: 'vfs', - path: '/test-plugin.zip', + path: '/test-plugin-0.0.1.zip', }, }, ], From 9c34cf4fc5336b6e90e28923bf13a63ac5b65f6c Mon Sep 17 00:00:00 2001 From: eliot-akira Date: Wed, 24 May 2023 21:42:30 +0200 Subject: [PATCH 05/43] Remove test plugin after install --- .../playground/blueprints/src/lib/steps/install-plugin.spec.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/playground/blueprints/src/lib/steps/install-plugin.spec.ts b/packages/playground/blueprints/src/lib/steps/install-plugin.spec.ts index 71ea16845a..fa88b8b4dd 100644 --- a/packages/playground/blueprints/src/lib/steps/install-plugin.spec.ts +++ b/packages/playground/blueprints/src/lib/steps/install-plugin.spec.ts @@ -46,6 +46,8 @@ describe('Blueprint step installPlugin', () => { php ); + php.unlink('/test-plugin-0.0.1.zip'); + expect( php.fileExists(`${php.documentRoot}/wp-content/test-plugin`) ).toBe(true); From 89a627d84092c6d2ec4a8e28a33158f79c159a0d Mon Sep 17 00:00:00 2001 From: eliot-akira Date: Wed, 24 May 2023 23:22:08 +0200 Subject: [PATCH 06/43] Use unzip step and find extracted plugin folder name --- .../src/lib/steps/install-plugin.spec.ts | 10 +- .../src/lib/steps/install-plugin.ts | 179 +++++++++++------- 2 files changed, 115 insertions(+), 74 deletions(-) diff --git a/packages/playground/blueprints/src/lib/steps/install-plugin.spec.ts b/packages/playground/blueprints/src/lib/steps/install-plugin.spec.ts index fa88b8b4dd..c880517389 100644 --- a/packages/playground/blueprints/src/lib/steps/install-plugin.spec.ts +++ b/packages/playground/blueprints/src/lib/steps/install-plugin.spec.ts @@ -22,14 +22,16 @@ describe('Blueprint step installPlugin', () => { `/**\n * Plugin Name: Test Plugin` ); + const zipFileName = 'test-plugin-0.0.1.zip' + await php.run({ - code: `open("test-plugin-0.0.1.zip", ZIPARCHIVE::CREATE); $zip->addFile("/tmp/test-plugin/index.php"); $zip->close();`, + code: `open("${zipFileName}", ZIPARCHIVE::CREATE); $zip->addFile("/tmp/test-plugin/index.php"); $zip->close();`, }); php.rmdir('/tmp/test-plugin'); // Note the package name is different from plugin folder name - expect(php.fileExists('/test-plugin-0.0.1.zip')).toBe(true); + expect(php.fileExists(zipFileName)).toBe(true); await runBlueprintSteps( compileBlueprint({ @@ -38,7 +40,7 @@ describe('Blueprint step installPlugin', () => { step: 'installPlugin', pluginZipFile: { resource: 'vfs', - path: '/test-plugin-0.0.1.zip', + path: zipFileName, }, }, ], @@ -46,7 +48,7 @@ describe('Blueprint step installPlugin', () => { php ); - php.unlink('/test-plugin-0.0.1.zip'); + php.unlink(zipFileName); expect( php.fileExists(`${php.documentRoot}/wp-content/test-plugin`) diff --git a/packages/playground/blueprints/src/lib/steps/install-plugin.ts b/packages/playground/blueprints/src/lib/steps/install-plugin.ts index 110713c922..2d13f0e7b3 100644 --- a/packages/playground/blueprints/src/lib/steps/install-plugin.ts +++ b/packages/playground/blueprints/src/lib/steps/install-plugin.ts @@ -3,6 +3,7 @@ import { StepHandler } from '.'; import { zipNameToHumanName } from './common'; import { writeFile } from './client-methods'; import { activatePlugin } from './activate-plugin'; +import { unzip } from './import-export'; /** * @inheritDoc installPlugin @@ -60,32 +61,63 @@ export const installPlugin: StepHandler> = async ( { pluginZipFile, options = {} }, progress? ) => { + + const zipFileName = pluginZipFile.name.split('/').pop() || 'plugin.zip' + const zipNiceName = zipNameToHumanName(zipFileName) + progress?.tracker.setCaption( - `Installing the ${zipNameToHumanName(pluginZipFile?.name)} plugin` + `Installing the ${zipNiceName} plugin` ); try { - const activate = 'activate' in options ? options.activate : true; - const pluginsPath = `${playground.documentRoot}/wp-content/plugins`; - const pluginZipPath = `${pluginsPath}/${pluginZipFile.name}`; + // Extract to temporary folder so we can find plugin folder name + + const tmpFolder = '/tmp/plugin' + const tmpZipPath = `/tmp/${zipFileName}`; + + if (await playground.isDir(tmpFolder)) { + await playground.unlink(tmpFolder); + } await writeFile(playground, { - path: pluginZipPath, + path: tmpZipPath, data: pluginZipFile, }); - await playground.run({ - code: `open('${pluginZipPath}'); -if ($res) { - $zip->extractTo('${pluginsPath}'); - $zip->close(); -} -`, + await unzip(playground, { + zipPath: tmpZipPath, + extractToPath: tmpFolder }); - const pluginPath = pluginZipPath.replace('.zip', ''); + // Find extracted plugin folder name + + const files = await playground.listFiles(tmpFolder); + + let pluginFolderName; + let tmpPluginPath = ''; + + for (const file of files) { + tmpPluginPath = `${tmpFolder}/${file}` + if (await playground.isDir(tmpPluginPath)) { + pluginFolderName = file; + break + } + } + + if (!pluginFolderName) { + throw new Error('Extracted plugin folder not found') + } + + // Move it to site plugins + + const pluginPath = `${playground.documentRoot}/wp-content/plugins/${pluginFolderName}`; + + await playground.mv( + tmpPluginPath, + pluginPath + ) + + const activate = 'activate' in options ? options.activate : true; if (activate) { await activatePlugin( @@ -97,69 +129,76 @@ if ($res) { ); } - /** - * Pair the site editor's nested iframe to the Service Worker. - * - * Without the patch below, the site editor initiates network requests that - * aren't routed through the service worker. That's a known browser issue: - * - * * https://bugs.chromium.org/p/chromium/issues/detail?id=880768 - * * https://bugzilla.mozilla.org/show_bug.cgi?id=1293277 - * * https://github.com/w3c/ServiceWorker/issues/765 - * - * The problem with iframes using srcDoc and src="about:blank" as they - * fail to inherit the root site's service worker. - * - * Gutenberg loads the site editor using