From 6c54e29cd32558a052da76ef29ef00139d0e5191 Mon Sep 17 00:00:00 2001 From: Brandon Payton Date: Thu, 28 Nov 2024 05:26:24 -0500 Subject: [PATCH] Add installPlugin support for single plugin files (#2033) ## Motivation for the change, related issues When testing #2029, I wanted to be able to install a plugin from a single PHP file and have it activated as part of the same step. This seemed reasonable, so I added support. ## Implementation details Prior to this PR, if the `installPlugin` step received a file resource it assumed it was a Zip file. This PR generally continues that practice but makes an exception for file names ending with a `.php` extension. File with names ending with `.php` are simply written to `wp-content/plugins/`. ## Testing Instructions (or ideally a Blueprint) CI - this PR adds a relevant unit test --- .../public/blueprint-schema-validator.js | 4 +- .../blueprints/public/blueprint-schema.json | 2 +- .../src/lib/steps/install-plugin.spec.ts | 23 +++++++- .../src/lib/steps/install-plugin.ts | 58 +++++++++++++------ 4 files changed, 64 insertions(+), 23 deletions(-) diff --git a/packages/playground/blueprints/public/blueprint-schema-validator.js b/packages/playground/blueprints/public/blueprint-schema-validator.js index 598401f771..299fc51480 100644 --- a/packages/playground/blueprints/public/blueprint-schema-validator.js +++ b/packages/playground/blueprints/public/blueprint-schema-validator.js @@ -535,7 +535,7 @@ const schema11 = { { $ref: '#/definitions/DirectoryReference' }, ], description: - 'The plugin files to install. It can be either a plugin zip file, or a directory containing all the plugin files at its root.', + 'The plugin files to install. It can be a plugin zip file, a single PHP file, or a directory containing all the plugin files at its root.', }, pluginZipFile: { $ref: '#/definitions/FileReference', @@ -3225,7 +3225,7 @@ const schema22 = { { $ref: '#/definitions/DirectoryReference' }, ], description: - 'The plugin files to install. It can be either a plugin zip file, or a directory containing all the plugin files at its root.', + 'The plugin files to install. It can be a plugin zip file, a single PHP file, or a directory containing all the plugin files at its root.', }, pluginZipFile: { $ref: '#/definitions/FileReference', diff --git a/packages/playground/blueprints/public/blueprint-schema.json b/packages/playground/blueprints/public/blueprint-schema.json index a59a25460d..0a8f058dca 100644 --- a/packages/playground/blueprints/public/blueprint-schema.json +++ b/packages/playground/blueprints/public/blueprint-schema.json @@ -647,7 +647,7 @@ "$ref": "#/definitions/DirectoryReference" } ], - "description": "The plugin files to install. It can be either a plugin zip file, or a directory containing all the plugin files at its root." + "description": "The plugin files to install. It can be a plugin zip file, a single PHP file, or a directory containing all the plugin files at its root." }, "pluginZipFile": { "$ref": "#/definitions/FileReference", 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 f6d7ad1051..134ee9966f 100644 --- a/packages/playground/blueprints/src/lib/steps/install-plugin.spec.ts +++ b/packages/playground/blueprints/src/lib/steps/install-plugin.spec.ts @@ -68,6 +68,7 @@ describe('Blueprint step installPlugin', () => { let php: PHP; // Create plugins folder let rootPath = ''; + let pluginsPath = ''; let installedPluginPath = ''; const pluginName = 'test-plugin'; const zipFileName = `${pluginName}-0.0.1.zip`; @@ -80,8 +81,9 @@ describe('Blueprint step installPlugin', () => { php = await handler.getPrimaryPhp(); rootPath = php.documentRoot; - php.mkdir(`${rootPath}/wp-content/plugins`); - installedPluginPath = `${rootPath}/wp-content/plugins/${pluginName}`; + pluginsPath = `${rootPath}/wp-content/plugins`; + php.mkdir(pluginsPath); + installedPluginPath = `${pluginsPath}/${pluginName}`; }); it('should install a plugin', async () => { @@ -97,6 +99,23 @@ describe('Blueprint step installPlugin', () => { expect(php.fileExists(installedPluginPath)).toBe(true); }); + it('should install a single PHP file as a plugin', async () => { + const rawPluginContent = ` { // @ts-ignore await installPlugin(php, { diff --git a/packages/playground/blueprints/src/lib/steps/install-plugin.ts b/packages/playground/blueprints/src/lib/steps/install-plugin.ts index 4a8f72882d..261d0693f9 100644 --- a/packages/playground/blueprints/src/lib/steps/install-plugin.ts +++ b/packages/playground/blueprints/src/lib/steps/install-plugin.ts @@ -1,6 +1,7 @@ import { StepHandler } from '.'; import { InstallAssetOptions, installAsset } from './install-asset'; import { activatePlugin } from './activate-plugin'; +import { writeFile } from './write-file'; import { zipNameToHumanName } from '../utils/zip-name-to-human-name'; import { Directory } from '../resources'; import { joinPaths } from '@php-wasm/util'; @@ -51,8 +52,8 @@ export interface InstallPluginStep */ step: 'installPlugin'; /** - * The plugin files to install. It can be either a plugin zip file, or a - * directory containing all the plugin files at its root. + * The plugin files to install. It can be a plugin zip file, a single PHP + * file, or a directory containing all the plugin files at its root. */ pluginData: FileResource | DirectoryResource; @@ -99,31 +100,52 @@ export const installPlugin: StepHandler< ); } - const targetFolderName = 'targetFolderName' in options ? options.targetFolderName : ''; + const pluginsDirectoryPath = joinPaths( + await playground.documentRoot, + 'wp-content', + 'plugins' + ); + const targetFolderName = + 'targetFolderName' in options ? options.targetFolderName : ''; let assetFolderPath = ''; let assetNiceName = ''; if (pluginData instanceof File) { - // @TODO: Consider validating whether this is a zip file? - const zipFileName = pluginData.name.split('/').pop() || 'plugin.zip'; - assetNiceName = zipNameToHumanName(zipFileName); + if (pluginData.name.endsWith('.php')) { + const destinationFilePath = joinPaths( + pluginsDirectoryPath, + pluginData.name + ); + await writeFile(playground, { + path: destinationFilePath, + data: pluginData, + }); + assetFolderPath = pluginsDirectoryPath; + assetNiceName = pluginData.name; + } else { + // Assume any other file is a zip file + // @TODO: Consider validating whether this is a zip file? + const zipFileName = + pluginData.name.split('/').pop() || 'plugin.zip'; + assetNiceName = zipNameToHumanName(zipFileName); - progress?.tracker.setCaption(`Installing the ${assetNiceName} plugin`); - const assetResult = await installAsset(playground, { - ifAlreadyInstalled, - zipFile: pluginData, - targetPath: `${await playground.documentRoot}/wp-content/plugins`, - targetFolderName: targetFolderName - }); - assetFolderPath = assetResult.assetFolderPath; - assetNiceName = assetResult.assetFolderName; + progress?.tracker.setCaption( + `Installing the ${assetNiceName} plugin` + ); + const assetResult = await installAsset(playground, { + ifAlreadyInstalled, + zipFile: pluginData, + targetPath: `${await playground.documentRoot}/wp-content/plugins`, + targetFolderName: targetFolderName, + }); + assetFolderPath = assetResult.assetFolderPath; + assetNiceName = assetResult.assetFolderName; + } } else if (pluginData) { assetNiceName = pluginData.name; progress?.tracker.setCaption(`Installing the ${assetNiceName} plugin`); const pluginDirectoryPath = joinPaths( - await playground.documentRoot, - 'wp-content', - 'plugins', + pluginsDirectoryPath, targetFolderName || pluginData.name ); await writeFiles(playground, pluginDirectoryPath, pluginData.files, {