Skip to content

Commit 6c54e29

Browse files
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/<filename>`. ## Testing Instructions (or ideally a Blueprint) CI - this PR adds a relevant unit test
1 parent 986b0d9 commit 6c54e29

File tree

4 files changed

+64
-23
lines changed

4 files changed

+64
-23
lines changed

packages/playground/blueprints/public/blueprint-schema-validator.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -535,7 +535,7 @@ const schema11 = {
535535
{ $ref: '#/definitions/DirectoryReference' },
536536
],
537537
description:
538-
'The plugin files to install. It can be either a plugin zip file, or a directory containing all the plugin files at its root.',
538+
'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.',
539539
},
540540
pluginZipFile: {
541541
$ref: '#/definitions/FileReference',
@@ -3225,7 +3225,7 @@ const schema22 = {
32253225
{ $ref: '#/definitions/DirectoryReference' },
32263226
],
32273227
description:
3228-
'The plugin files to install. It can be either a plugin zip file, or a directory containing all the plugin files at its root.',
3228+
'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.',
32293229
},
32303230
pluginZipFile: {
32313231
$ref: '#/definitions/FileReference',

packages/playground/blueprints/public/blueprint-schema.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -647,7 +647,7 @@
647647
"$ref": "#/definitions/DirectoryReference"
648648
}
649649
],
650-
"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."
650+
"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."
651651
},
652652
"pluginZipFile": {
653653
"$ref": "#/definitions/FileReference",

packages/playground/blueprints/src/lib/steps/install-plugin.spec.ts

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@ describe('Blueprint step installPlugin', () => {
6868
let php: PHP;
6969
// Create plugins folder
7070
let rootPath = '';
71+
let pluginsPath = '';
7172
let installedPluginPath = '';
7273
const pluginName = 'test-plugin';
7374
const zipFileName = `${pluginName}-0.0.1.zip`;
@@ -80,8 +81,9 @@ describe('Blueprint step installPlugin', () => {
8081
php = await handler.getPrimaryPhp();
8182

8283
rootPath = php.documentRoot;
83-
php.mkdir(`${rootPath}/wp-content/plugins`);
84-
installedPluginPath = `${rootPath}/wp-content/plugins/${pluginName}`;
84+
pluginsPath = `${rootPath}/wp-content/plugins`;
85+
php.mkdir(pluginsPath);
86+
installedPluginPath = `${pluginsPath}/${pluginName}`;
8587
});
8688

8789
it('should install a plugin', async () => {
@@ -97,6 +99,23 @@ describe('Blueprint step installPlugin', () => {
9799
expect(php.fileExists(installedPluginPath)).toBe(true);
98100
});
99101

102+
it('should install a single PHP file as a plugin', async () => {
103+
const rawPluginContent = `<?php\n/**\n * Plugin Name: Test Plugin`;
104+
await installPlugin(php, {
105+
pluginData: new File(
106+
[new TextEncoder().encode(rawPluginContent)],
107+
'test-plugin.php'
108+
),
109+
ifAlreadyInstalled: 'overwrite',
110+
options: {
111+
activate: false,
112+
},
113+
});
114+
const pluginFilePath = `${pluginsPath}/test-plugin.php`;
115+
expect(php.fileExists(pluginFilePath)).toBe(true);
116+
expect(php.readFileAsText(pluginFilePath)).toBe(rawPluginContent);
117+
});
118+
100119
it('should install a plugin using the deprecated pluginZipFile option', async () => {
101120
// @ts-ignore
102121
await installPlugin(php, {

packages/playground/blueprints/src/lib/steps/install-plugin.ts

Lines changed: 40 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { StepHandler } from '.';
22
import { InstallAssetOptions, installAsset } from './install-asset';
33
import { activatePlugin } from './activate-plugin';
4+
import { writeFile } from './write-file';
45
import { zipNameToHumanName } from '../utils/zip-name-to-human-name';
56
import { Directory } from '../resources';
67
import { joinPaths } from '@php-wasm/util';
@@ -51,8 +52,8 @@ export interface InstallPluginStep<FileResource, DirectoryResource>
5152
*/
5253
step: 'installPlugin';
5354
/**
54-
* The plugin files to install. It can be either a plugin zip file, or a
55-
* directory containing all the plugin files at its root.
55+
* The plugin files to install. It can be a plugin zip file, a single PHP
56+
* file, or a directory containing all the plugin files at its root.
5657
*/
5758
pluginData: FileResource | DirectoryResource;
5859

@@ -99,31 +100,52 @@ export const installPlugin: StepHandler<
99100
);
100101
}
101102

102-
const targetFolderName = 'targetFolderName' in options ? options.targetFolderName : '';
103+
const pluginsDirectoryPath = joinPaths(
104+
await playground.documentRoot,
105+
'wp-content',
106+
'plugins'
107+
);
108+
const targetFolderName =
109+
'targetFolderName' in options ? options.targetFolderName : '';
103110
let assetFolderPath = '';
104111
let assetNiceName = '';
105112
if (pluginData instanceof File) {
106-
// @TODO: Consider validating whether this is a zip file?
107-
const zipFileName = pluginData.name.split('/').pop() || 'plugin.zip';
108-
assetNiceName = zipNameToHumanName(zipFileName);
113+
if (pluginData.name.endsWith('.php')) {
114+
const destinationFilePath = joinPaths(
115+
pluginsDirectoryPath,
116+
pluginData.name
117+
);
118+
await writeFile(playground, {
119+
path: destinationFilePath,
120+
data: pluginData,
121+
});
122+
assetFolderPath = pluginsDirectoryPath;
123+
assetNiceName = pluginData.name;
124+
} else {
125+
// Assume any other file is a zip file
126+
// @TODO: Consider validating whether this is a zip file?
127+
const zipFileName =
128+
pluginData.name.split('/').pop() || 'plugin.zip';
129+
assetNiceName = zipNameToHumanName(zipFileName);
109130

110-
progress?.tracker.setCaption(`Installing the ${assetNiceName} plugin`);
111-
const assetResult = await installAsset(playground, {
112-
ifAlreadyInstalled,
113-
zipFile: pluginData,
114-
targetPath: `${await playground.documentRoot}/wp-content/plugins`,
115-
targetFolderName: targetFolderName
116-
});
117-
assetFolderPath = assetResult.assetFolderPath;
118-
assetNiceName = assetResult.assetFolderName;
131+
progress?.tracker.setCaption(
132+
`Installing the ${assetNiceName} plugin`
133+
);
134+
const assetResult = await installAsset(playground, {
135+
ifAlreadyInstalled,
136+
zipFile: pluginData,
137+
targetPath: `${await playground.documentRoot}/wp-content/plugins`,
138+
targetFolderName: targetFolderName,
139+
});
140+
assetFolderPath = assetResult.assetFolderPath;
141+
assetNiceName = assetResult.assetFolderName;
142+
}
119143
} else if (pluginData) {
120144
assetNiceName = pluginData.name;
121145
progress?.tracker.setCaption(`Installing the ${assetNiceName} plugin`);
122146

123147
const pluginDirectoryPath = joinPaths(
124-
await playground.documentRoot,
125-
'wp-content',
126-
'plugins',
148+
pluginsDirectoryPath,
127149
targetFolderName || pluginData.name
128150
);
129151
await writeFiles(playground, pluginDirectoryPath, pluginData.files, {

0 commit comments

Comments
 (0)