-
Notifications
You must be signed in to change notification settings - Fork 298
Remove DOMParser from Blueprints: installPlugin and installTheme #427
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
815acec
61e33f3
7eb0e26
304e256
9c34cf4
89a627d
d53a55a
98cde65
bfaef98
3f8eb82
f2ed29b
7840907
daf74b3
82ad36e
3520de3
579c568
534d10f
0615596
11bee55
6080b0d
fa4f559
3fce74e
2b8d31b
e5ae9b5
3dbd17a
ca7b8c1
9eed478
56cb9d3
28f7163
dc05730
81bb68f
a368526
7d4f233
30d5c9a
9ed3d75
3b67796
c121866
661eda9
3cbea6a
4487c3a
6b0f60f
1671b88
3526f57
6f81a34
977230f
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,11 +1,7 @@ | ||
import type { PHPResponse, UniversalPHP } from '@php-wasm/universal'; | ||
|
||
export function asDOM(response: PHPResponse) { | ||
return new DOMParser().parseFromString(response.text, 'text/html')!; | ||
} | ||
import type { UniversalPHP } from '@php-wasm/universal'; | ||
|
||
export function zipNameToHumanName(zipName: string) { | ||
const mixedCaseName = zipName.split('.').shift()!.replace('-', ' '); | ||
const mixedCaseName = zipName.split('.').shift()!.replace(/-/g, ' '); | ||
return ( | ||
mixedCaseName.charAt(0).toUpperCase() + | ||
mixedCaseName.slice(1).toLowerCase() | ||
|
@@ -28,3 +24,31 @@ export async function updateFile( | |
export async function fileToUint8Array(file: File) { | ||
return new Uint8Array(await file.arrayBuffer()); | ||
} | ||
|
||
/** | ||
* Polyfill the File class in JSDOM which lacks arrayBuffer() method | ||
* | ||
* - [Implement Blob.stream, Blob.text and Blob.arrayBuffer](https://github.com/jsdom/jsdom/issues/2555) | ||
* | ||
* When a Resource (../resources.ts) resolves to an instance of File, the | ||
* resulting object is missing the arrayBuffer() method in JSDOM environment | ||
* during tests. | ||
* | ||
* Import the polyfilled File class below to ensure its buffer is available to | ||
* functions like writeFile (./client-methods.ts) and fileToUint8Array (above). | ||
*/ | ||
class FilePolyfill extends File { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ideally this would live in tests, but that makes importing Blueprints in other packages tricky as every package would have to polyfill it. I think this is a neat solution, thank you for taking the time to explore this 👍 |
||
buffers: BlobPart[]; | ||
constructor(buffers: BlobPart[], name: string) { | ||
super(buffers, name); | ||
this.buffers = buffers; | ||
} | ||
override async arrayBuffer(): Promise<ArrayBuffer> { | ||
return this.buffers[0] as ArrayBuffer; | ||
} | ||
} | ||
|
||
const FileWithArrayBuffer = | ||
File.prototype.arrayBuffer instanceof Function ? File : FilePolyfill; | ||
|
||
export { FileWithArrayBuffer as File }; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,96 @@ | ||
import type { UniversalPHP } from '@php-wasm/universal'; | ||
import { writeFile } from './client-methods'; | ||
import { unzip } from './import-export'; | ||
|
||
export interface InstallAssetOptions { | ||
/** | ||
* The zip file to install. | ||
*/ | ||
zipFile: File; | ||
/** | ||
* Target path to extract the main folder. | ||
* @example | ||
* | ||
* <code> | ||
* const targetPath = `${await playground.documentRoot}/wp-content/plugins`; | ||
* </code> | ||
*/ | ||
targetPath: string; | ||
} | ||
|
||
/** | ||
* Install asset: Extract folder from zip file and move it to target | ||
*/ | ||
export async function installAsset( | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I initially had this in mind as an internal utility to aid other steps, I wonder whether this makes sense as its own step 🤔 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The term "asset" seems ambiguous as part of a public API. Internally it means:
Technically it's generic, not limited to plugins and themes - but I can't imagine what else would be considered "assets" that has the above characteristics. There could be an Media files could be another kind of asset. Maybe an So I think There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Oh sorry I got confused. It is in the steps directory and kind of looks like a step declaration, but it actually is an internal helper. My bad! There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It would be nice to put each step in a separate file and move helpers somewhere distinct, maybe a subdirectory? Let's figure this out separately |
||
playground: UniversalPHP, | ||
{ targetPath, zipFile }: InstallAssetOptions | ||
): Promise<{ | ||
assetFolderPath: string; | ||
assetFolderName: string; | ||
}> { | ||
// Extract to temporary folder so we can find asset folder name | ||
|
||
const zipFileName = zipFile.name; | ||
const tmpFolder = `/tmp/assets`; | ||
const tmpZipPath = `/tmp/${zipFileName}`; | ||
|
||
const removeTmpFolder = () => | ||
playground.rmdir(tmpFolder, { | ||
recursive: true, | ||
}); | ||
|
||
if (await playground.fileExists(tmpFolder)) { | ||
await removeTmpFolder(); | ||
} | ||
|
||
await writeFile(playground, { | ||
path: tmpZipPath, | ||
data: zipFile, | ||
}); | ||
|
||
const cleanup = () => | ||
Promise.all([removeTmpFolder, () => playground.unlink(tmpZipPath)]); | ||
|
||
try { | ||
await unzip(playground, { | ||
zipPath: tmpZipPath, | ||
extractToPath: tmpFolder, | ||
}); | ||
|
||
// Find extracted asset folder name | ||
|
||
const files = await playground.listFiles(tmpFolder); | ||
|
||
let assetFolderName; | ||
let tmpAssetPath = ''; | ||
|
||
for (const file of files) { | ||
tmpAssetPath = `${tmpFolder}/${file}`; | ||
if (await playground.isDir(tmpAssetPath)) { | ||
assetFolderName = file; | ||
break; | ||
} | ||
} | ||
|
||
if (!assetFolderName) { | ||
throw new Error( | ||
`The zip file should contain a single folder with files inside, but the provided zip file (${zipFileName}) does not contain such a folder.` | ||
); | ||
} | ||
|
||
// Move asset folder to target path | ||
|
||
const assetFolderPath = `${targetPath}/${assetFolderName}`; | ||
await playground.mv(tmpAssetPath, assetFolderPath); | ||
|
||
await cleanup(); | ||
|
||
return { | ||
assetFolderPath, | ||
assetFolderName, | ||
}; | ||
} catch (error) { | ||
await cleanup(); | ||
throw error; | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,66 @@ | ||
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: '/wordpress', | ||
isStaticFilePath: (path) => !path.endsWith('.php'), | ||
}, | ||
}); | ||
}); | ||
|
||
it('should install a plugin', async () => { | ||
// Create test plugin | ||
|
||
const pluginName = 'test-plugin'; | ||
|
||
php.mkdir(`/${pluginName}`); | ||
php.writeFile( | ||
`/${pluginName}/index.php`, | ||
`/**\n * Plugin Name: Test Plugin` | ||
); | ||
|
||
// Note the package name is different from plugin folder name | ||
const zipFileName = `${pluginName}-0.0.1.zip`; | ||
|
||
await php.run({ | ||
code: `<?php $zip = new ZipArchive(); $zip->open("${zipFileName}", ZIPARCHIVE::CREATE); $zip->addFile("/${pluginName}/index.php"); $zip->close();`, | ||
}); | ||
|
||
php.rmdir(`/${pluginName}`); | ||
|
||
expect(php.fileExists(zipFileName)).toBe(true); | ||
|
||
// Create plugins folder | ||
const rootPath = await php.documentRoot; | ||
const pluginsPath = `${rootPath}/wp-content/plugins`; | ||
|
||
php.mkdir(pluginsPath); | ||
|
||
await runBlueprintSteps( | ||
compileBlueprint({ | ||
steps: [ | ||
{ | ||
step: 'installPlugin', | ||
pluginZipFile: { | ||
resource: 'vfs', | ||
path: zipFileName, | ||
}, | ||
options: { | ||
activate: false, | ||
}, | ||
}, | ||
], | ||
}), | ||
php | ||
); | ||
|
||
php.unlink(zipFileName); | ||
|
||
expect(php.fileExists(`${pluginsPath}/${pluginName}`)).toBe(true); | ||
}); | ||
}); |
Uh oh!
There was an error while loading. Please reload this page.