From 124f62d0cbcabbb3d8df2733a7d7a32fc69f547d Mon Sep 17 00:00:00 2001 From: sejas Date: Mon, 19 Jun 2023 13:12:22 +0100 Subject: [PATCH 01/23] wp-now: add blueprint parameter --- packages/wp-now/src/run-cli.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/packages/wp-now/src/run-cli.ts b/packages/wp-now/src/run-cli.ts index 72429c1f..098ac55b 100644 --- a/packages/wp-now/src/run-cli.ts +++ b/packages/wp-now/src/run-cli.ts @@ -67,6 +67,10 @@ export async function runCli() { describe: 'Server port', type: 'number', }); + yargs.option('blueprint', { + describe: 'Path to a blueprint file to be executed', + type: 'string', + }); }, async (argv) => { const spinner = startSpinner('Starting the server...'); @@ -76,6 +80,7 @@ export async function runCli() { php: argv.php as SupportedPHPVersion, wp: argv.wp as string, port: argv.port as number, + blueprint: argv.blueprint as string, }); portFinder.setPort(options.port as number); const { url } = await startServer(options); From 3d2b4a687ab6206aa570fa772b1b8367e44465f0 Mon Sep 17 00:00:00 2001 From: sejas Date: Mon, 19 Jun 2023 13:12:48 +0100 Subject: [PATCH 02/23] wp-now: read and parse blueprint on config --- packages/wp-now/src/config.ts | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/packages/wp-now/src/config.ts b/packages/wp-now/src/config.ts index 52b8af60..fd356dc9 100644 --- a/packages/wp-now/src/config.ts +++ b/packages/wp-now/src/config.ts @@ -3,14 +3,14 @@ import { SupportedPHPVersionsList, } from '@php-wasm/universal'; import crypto from 'crypto'; +import fs from 'fs-extra'; +import path from 'path'; +import { Blueprint } from '@wp-playground/blueprints'; import { getCodeSpaceURL, isGitHubCodespace } from './github-codespaces'; import { inferMode } from './wp-now'; import { portFinder } from './port-finder'; import { isValidWordPressVersion } from './wp-playground-wordpress'; import getWpNowPath from './get-wp-now-path'; - -import path from 'path'; - import { DEFAULT_PHP_VERSION, DEFAULT_WORDPRESS_VERSION } from './constants'; export interface CliOptions { @@ -18,6 +18,7 @@ export interface CliOptions { path?: string; wp?: string; port?: number; + blueprint?: string; } export const enum WPNowMode { @@ -41,6 +42,7 @@ export interface WPNowOptions { wpContentPath?: string; wordPressVersion?: string; numberOfPhpInstances?: number; + blueprintObject?: Blueprint; } export const DEFAULT_OPTIONS: WPNowOptions = { @@ -135,5 +137,17 @@ export default async function getWpNowConfig( }. Supported versions: ${SupportedPHPVersionsList.join(', ')}` ); } + if (args.blueprint) { + const blueprintPath = path.resolve(args.blueprint); + if (!fs.existsSync(blueprintPath)) { + throw new Error(`Blueprint file not found: ${blueprintPath}`); + } + const blueprintObject = JSON.parse( + fs.readFileSync(blueprintPath, 'utf8') + ); + // TODO: blueprint schema validation + + options.blueprintObject = blueprintObject; + } return options; } From 6602715d31e2f23441735910af8ea73d7c08ceb6 Mon Sep 17 00:00:00 2001 From: sejas Date: Mon, 19 Jun 2023 13:13:19 +0100 Subject: [PATCH 03/23] wp-now: Compile and execute blueprint steps --- packages/wp-now/src/wp-now.ts | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/packages/wp-now/src/wp-now.ts b/packages/wp-now/src/wp-now.ts index 4f628747..6cc447ea 100644 --- a/packages/wp-now/src/wp-now.ts +++ b/packages/wp-now/src/wp-now.ts @@ -8,10 +8,13 @@ import { downloadWordPress, } from './download'; import { + StepDefinition, activatePlugin, activateTheme, + compileBlueprint, defineWpConfigConsts, login, + runBlueprintSteps, } from '@wp-playground/blueprints'; import { WPNowOptions, WPNowMode } from './config'; import { @@ -132,6 +135,19 @@ export default async function startWPNow( await activatePluginOrTheme(php, options); } + if (options.blueprintObject) { + output.log(`blueprint steps: ${options.blueprintObject.steps.length}`); + const compiled = compileBlueprint(options.blueprintObject, { + onStepCompleted: (result, step: StepDefinition) => { + output.log(`Blueprint Step completed: ${step.step}`, { + result, + step, + }); + }, + }); + await runBlueprintSteps(compiled, php); + } + return { php, phpInstances, From 49dde3a6d7e775be16291dc8e5ff5e7ff3b41f02 Mon Sep 17 00:00:00 2001 From: sejas Date: Mon, 19 Jun 2023 18:19:40 +0100 Subject: [PATCH 04/23] wp-now: output only the step name --- packages/wp-now/src/wp-now.ts | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/packages/wp-now/src/wp-now.ts b/packages/wp-now/src/wp-now.ts index 6cc447ea..a94db8c0 100644 --- a/packages/wp-now/src/wp-now.ts +++ b/packages/wp-now/src/wp-now.ts @@ -139,10 +139,7 @@ export default async function startWPNow( output.log(`blueprint steps: ${options.blueprintObject.steps.length}`); const compiled = compileBlueprint(options.blueprintObject, { onStepCompleted: (result, step: StepDefinition) => { - output.log(`Blueprint Step completed: ${step.step}`, { - result, - step, - }); + output.log(`Blueprint Step completed: ${step.step}`); }, }); await runBlueprintSteps(compiled, php); From f616148e3b5131a070de53501ce81b0daf3f1e58 Mon Sep 17 00:00:00 2001 From: sejas Date: Tue, 20 Jun 2023 09:58:41 +0100 Subject: [PATCH 05/23] wp-now: move blueprint executinon before any php request to avoid php ini entry two times --- packages/wp-now/src/wp-now.ts | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/packages/wp-now/src/wp-now.ts b/packages/wp-now/src/wp-now.ts index a94db8c0..ed8c364c 100644 --- a/packages/wp-now/src/wp-now.ts +++ b/packages/wp-now/src/wp-now.ts @@ -122,6 +122,16 @@ export default async function startWPNow( } }); + if (options.blueprintObject) { + output.log(`blueprint steps: ${options.blueprintObject.steps.length}`); + const compiled = compileBlueprint(options.blueprintObject, { + onStepCompleted: (result, step: StepDefinition) => { + output.log(`Blueprint Step completed: ${step.step}`); + }, + }); + await runBlueprintSteps(compiled, php); + } + await installationStep2(php); await login(php, { username: 'admin', @@ -135,16 +145,6 @@ export default async function startWPNow( await activatePluginOrTheme(php, options); } - if (options.blueprintObject) { - output.log(`blueprint steps: ${options.blueprintObject.steps.length}`); - const compiled = compileBlueprint(options.blueprintObject, { - onStepCompleted: (result, step: StepDefinition) => { - output.log(`Blueprint Step completed: ${step.step}`); - }, - }); - await runBlueprintSteps(compiled, php); - } - return { php, phpInstances, From 82011a5e61637ab558283bad37e4ccc229675a1d Mon Sep 17 00:00:00 2001 From: sejas Date: Tue, 20 Jun 2023 09:59:06 +0100 Subject: [PATCH 06/23] wp-now: avoid appending default port 80 --- packages/wp-now/src/config.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/packages/wp-now/src/config.ts b/packages/wp-now/src/config.ts index fd356dc9..5ed19b8c 100644 --- a/packages/wp-now/src/config.ts +++ b/packages/wp-now/src/config.ts @@ -70,7 +70,11 @@ async function getAbsoluteURL() { if (isGitHubCodespace) { return getCodeSpaceURL(port); } - return `http://localhost:${port}`; + const url = 'http://localhost'; + if (port === 80) { + return url; + } + return `${url}:${port}`; } function getWpContentHomePath(projectPath: string, mode: string) { From 498d7c7a9c73c4d016bca797841678870aec1559 Mon Sep 17 00:00:00 2001 From: sejas Date: Tue, 20 Jun 2023 10:54:43 +0100 Subject: [PATCH 07/23] wp-now: use blueprint WP_SITEURL to overwrite options.absoluteUrl --- packages/wp-now/src/wp-now.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/packages/wp-now/src/wp-now.ts b/packages/wp-now/src/wp-now.ts index ed8c364c..c682cc42 100644 --- a/packages/wp-now/src/wp-now.ts +++ b/packages/wp-now/src/wp-now.ts @@ -126,7 +126,10 @@ export default async function startWPNow( output.log(`blueprint steps: ${options.blueprintObject.steps.length}`); const compiled = compileBlueprint(options.blueprintObject, { onStepCompleted: (result, step: StepDefinition) => { - output.log(`Blueprint Step completed: ${step.step}`); + output.log(`Blueprint step completed: ${step.step}`); + if (step.step === 'defineWpConfigConsts' && step.consts.WP_SITEURL) { + options.absoluteUrl = step.consts.WP_SITEURL as string; + } }, }); await runBlueprintSteps(compiled, php); From 753eb738a3ce86867bdb65a540c05f9dd37dbcb7 Mon Sep 17 00:00:00 2001 From: sejas Date: Tue, 20 Jun 2023 10:55:04 +0100 Subject: [PATCH 08/23] wp-now: use blueprint WP_SITEURL to overwrite options.absoluteUrl --- packages/wp-now/src/wp-now.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/packages/wp-now/src/wp-now.ts b/packages/wp-now/src/wp-now.ts index c682cc42..1b874d74 100644 --- a/packages/wp-now/src/wp-now.ts +++ b/packages/wp-now/src/wp-now.ts @@ -127,7 +127,10 @@ export default async function startWPNow( const compiled = compileBlueprint(options.blueprintObject, { onStepCompleted: (result, step: StepDefinition) => { output.log(`Blueprint step completed: ${step.step}`); - if (step.step === 'defineWpConfigConsts' && step.consts.WP_SITEURL) { + if ( + step.step === 'defineWpConfigConsts' && + step.consts.WP_SITEURL + ) { options.absoluteUrl = step.consts.WP_SITEURL as string; } }, From f9ef3093ea20717fb1d1517f9ab37f03abe5399a Mon Sep 17 00:00:00 2001 From: sejas Date: Tue, 20 Jun 2023 12:37:45 +0100 Subject: [PATCH 09/23] wp-now: apply same blueprint logic to defineSiteUrl --- packages/wp-now/src/wp-now.ts | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/packages/wp-now/src/wp-now.ts b/packages/wp-now/src/wp-now.ts index 1b874d74..dd109f34 100644 --- a/packages/wp-now/src/wp-now.ts +++ b/packages/wp-now/src/wp-now.ts @@ -126,13 +126,16 @@ export default async function startWPNow( output.log(`blueprint steps: ${options.blueprintObject.steps.length}`); const compiled = compileBlueprint(options.blueprintObject, { onStepCompleted: (result, step: StepDefinition) => { - output.log(`Blueprint step completed: ${step.step}`); if ( step.step === 'defineWpConfigConsts' && step.consts.WP_SITEURL ) { - options.absoluteUrl = step.consts.WP_SITEURL as string; + options.absoluteUrl = `${step.consts.WP_SITEURL}`; + } else if (step.step === 'defineSiteUrl') { + options.absoluteUrl = step.siteUrl; } + + output.log(`Blueprint step completed: ${step.step}`); }, }); await runBlueprintSteps(compiled, php); From 83b77e2ec02bb5c470c791203e8d395d7a61a5db Mon Sep 17 00:00:00 2001 From: sejas Date: Tue, 20 Jun 2023 14:18:04 +0100 Subject: [PATCH 10/23] wp-now: set siteUrl from blueprint to work remotely --- packages/wp-now/src/config.ts | 32 ++++++++++++++++++++++++++++++++ packages/wp-now/src/wp-now.ts | 10 +--------- 2 files changed, 33 insertions(+), 9 deletions(-) diff --git a/packages/wp-now/src/config.ts b/packages/wp-now/src/config.ts index 5ed19b8c..7871c00b 100644 --- a/packages/wp-now/src/config.ts +++ b/packages/wp-now/src/config.ts @@ -65,11 +65,18 @@ export interface WPEnvOptions { mappings: Object; } +let absoluteUrlFromBlueprint = '' + async function getAbsoluteURL() { const port = await portFinder.getOpenPort(); if (isGitHubCodespace) { return getCodeSpaceURL(port); } + + if (absoluteUrlFromBlueprint) { + return absoluteUrlFromBlueprint; + } + const url = 'http://localhost'; if (port === 80) { return url; @@ -152,6 +159,31 @@ export default async function getWpNowConfig( // TODO: blueprint schema validation options.blueprintObject = blueprintObject; + const siteUrl = extractSiteUrlFromBlueprint(blueprintObject); + if (siteUrl) { + options.absoluteUrl = siteUrl; + absoluteUrlFromBlueprint = siteUrl; + } + } return options; } + + +function extractSiteUrlFromBlueprint(blueprintObject: Blueprint): string | false { + for (const step of blueprintObject.steps) { + if (typeof step !== 'object') { + return false; + } + + if (step.step === 'defineSiteUrl') { + return `${step.siteUrl}`; + }else if ( + step.step === 'defineWpConfigConsts' && + step.consts.WP_SITEURL + ) { + return `${step.consts.WP_SITEURL}`; + } + } + return false; +} diff --git a/packages/wp-now/src/wp-now.ts b/packages/wp-now/src/wp-now.ts index dd109f34..e20e4803 100644 --- a/packages/wp-now/src/wp-now.ts +++ b/packages/wp-now/src/wp-now.ts @@ -8,6 +8,7 @@ import { downloadWordPress, } from './download'; import { + Blueprint, StepDefinition, activatePlugin, activateTheme, @@ -126,15 +127,6 @@ export default async function startWPNow( output.log(`blueprint steps: ${options.blueprintObject.steps.length}`); const compiled = compileBlueprint(options.blueprintObject, { onStepCompleted: (result, step: StepDefinition) => { - if ( - step.step === 'defineWpConfigConsts' && - step.consts.WP_SITEURL - ) { - options.absoluteUrl = `${step.consts.WP_SITEURL}`; - } else if (step.step === 'defineSiteUrl') { - options.absoluteUrl = step.siteUrl; - } - output.log(`Blueprint step completed: ${step.step}`); }, }); From 9cdae2a378aed35a87886ef27c23a0ce2cd33cbe Mon Sep 17 00:00:00 2001 From: sejas Date: Tue, 20 Jun 2023 14:18:21 +0100 Subject: [PATCH 11/23] wp-now: code style --- packages/wp-now/src/config.ts | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/packages/wp-now/src/config.ts b/packages/wp-now/src/config.ts index 7871c00b..cf5ac8cc 100644 --- a/packages/wp-now/src/config.ts +++ b/packages/wp-now/src/config.ts @@ -65,7 +65,7 @@ export interface WPEnvOptions { mappings: Object; } -let absoluteUrlFromBlueprint = '' +let absoluteUrlFromBlueprint = ''; async function getAbsoluteURL() { const port = await portFinder.getOpenPort(); @@ -164,13 +164,13 @@ export default async function getWpNowConfig( options.absoluteUrl = siteUrl; absoluteUrlFromBlueprint = siteUrl; } - } return options; } - -function extractSiteUrlFromBlueprint(blueprintObject: Blueprint): string | false { +function extractSiteUrlFromBlueprint( + blueprintObject: Blueprint +): string | false { for (const step of blueprintObject.steps) { if (typeof step !== 'object') { return false; @@ -178,9 +178,9 @@ function extractSiteUrlFromBlueprint(blueprintObject: Blueprint): string | false if (step.step === 'defineSiteUrl') { return `${step.siteUrl}`; - }else if ( - step.step === 'defineWpConfigConsts' && - step.consts.WP_SITEURL + } else if ( + step.step === 'defineWpConfigConsts' && + step.consts.WP_SITEURL ) { return `${step.consts.WP_SITEURL}`; } From f0e91f15abdbdfc11dbace1a7d7c24e1fd8a8301 Mon Sep 17 00:00:00 2001 From: sejas Date: Thu, 22 Jun 2023 18:42:00 +0200 Subject: [PATCH 12/23] Bump minimum Node version to v20 to be able to use CustomEvent and File classes --- .nvmrc | 2 +- packages/wp-now/src/main.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.nvmrc b/.nvmrc index 0e9dc6b5..2a59cf43 100644 --- a/.nvmrc +++ b/.nvmrc @@ -1 +1 @@ -v18.13.0 +v20.0.0 diff --git a/packages/wp-now/src/main.ts b/packages/wp-now/src/main.ts index 9ccbfc3a..b80b9e62 100644 --- a/packages/wp-now/src/main.ts +++ b/packages/wp-now/src/main.ts @@ -1,6 +1,6 @@ import { runCli } from './run-cli'; -const requiredMajorVersion = 18; +const requiredMajorVersion = 20; const currentNodeVersion = parseInt(process.versions?.node?.split('.')?.[0]); From 91244734995e703c107162dc9e1ee9764deed12d Mon Sep 17 00:00:00 2001 From: sejas Date: Fri, 23 Jun 2023 10:56:09 +0200 Subject: [PATCH 13/23] wp-now: consider case where output is undefined --- packages/wp-now/src/wp-now.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/wp-now/src/wp-now.ts b/packages/wp-now/src/wp-now.ts index e20e4803..dd2967a3 100644 --- a/packages/wp-now/src/wp-now.ts +++ b/packages/wp-now/src/wp-now.ts @@ -124,10 +124,10 @@ export default async function startWPNow( }); if (options.blueprintObject) { - output.log(`blueprint steps: ${options.blueprintObject.steps.length}`); + output?.log(`blueprint steps: ${options.blueprintObject.steps.length}`); const compiled = compileBlueprint(options.blueprintObject, { onStepCompleted: (result, step: StepDefinition) => { - output.log(`Blueprint step completed: ${step.step}`); + output?.log(`Blueprint step completed: ${step.step}`); }, }); await runBlueprintSteps(compiled, php); From 420ef765a0a786ea8700d70836e9ac4c44fbdc03 Mon Sep 17 00:00:00 2001 From: sejas Date: Fri, 23 Jun 2023 10:56:45 +0200 Subject: [PATCH 14/23] wp-now: add tests for blueprints --- .../wp-now/src/tests/blueprints/wp-config.json | 12 ++++++++++++ packages/wp-now/src/tests/wp-now.spec.ts | 15 +++++++++++++++ 2 files changed, 27 insertions(+) create mode 100644 packages/wp-now/src/tests/blueprints/wp-config.json diff --git a/packages/wp-now/src/tests/blueprints/wp-config.json b/packages/wp-now/src/tests/blueprints/wp-config.json new file mode 100644 index 00000000..ba93cf4f --- /dev/null +++ b/packages/wp-now/src/tests/blueprints/wp-config.json @@ -0,0 +1,12 @@ +{ + "steps": [ + { + "step": "defineWpConfigConsts", + "consts": { + "WP_HOME": "http://myurl.wpnow", + "WP_SITEURL": "http://myurl.wpnow" + }, + "virtualize": true + } + ] +} diff --git a/packages/wp-now/src/tests/wp-now.spec.ts b/packages/wp-now/src/tests/wp-now.spec.ts index 5b4babde..0348e806 100644 --- a/packages/wp-now/src/tests/wp-now.spec.ts +++ b/packages/wp-now/src/tests/wp-now.spec.ts @@ -737,6 +737,21 @@ describe('Test starting different modes', () => { expect(req.headers.get('content-encoding')).toBe(null); }); }); + + /** + * Test blueprints execution. + */ + describe('blueprints', () => { + const blueprintExamplesPath = path.join(__dirname, 'blueprints'); + test('setting wp-config variables such as WP_SITEURL through blueprint', async () => { + const options = await getWpNowConfig({ + blueprint: path.join(blueprintExamplesPath, 'wp-config.json'), + }); + const { url, stopServer } = await startServer(options); + expect(url).toMatch('http://myurl.wpnow'); + await stopServer(); + }); + }); }); /** From a1ecbfb912e8cf98c8f27141ecee59fb756822e9 Mon Sep 17 00:00:00 2001 From: sejas Date: Fri, 23 Jun 2023 11:12:15 +0200 Subject: [PATCH 15/23] wp-now: remove todo validate blueprint since it will happen on compileBlueprint fn --- packages/wp-now/src/config.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/wp-now/src/config.ts b/packages/wp-now/src/config.ts index cf5ac8cc..5f0999ea 100644 --- a/packages/wp-now/src/config.ts +++ b/packages/wp-now/src/config.ts @@ -156,7 +156,6 @@ export default async function getWpNowConfig( const blueprintObject = JSON.parse( fs.readFileSync(blueprintPath, 'utf8') ); - // TODO: blueprint schema validation options.blueprintObject = blueprintObject; const siteUrl = extractSiteUrlFromBlueprint(blueprintObject); From 0263f8e8d4ac436b29aa8fa379dfa5840057c2d7 Mon Sep 17 00:00:00 2001 From: sejas Date: Fri, 23 Jun 2023 11:40:38 +0200 Subject: [PATCH 16/23] wp-now: update blueprint tests --- packages/wp-now/src/tests/wp-now.spec.ts | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/packages/wp-now/src/tests/wp-now.spec.ts b/packages/wp-now/src/tests/wp-now.spec.ts index 0348e806..de645f82 100644 --- a/packages/wp-now/src/tests/wp-now.spec.ts +++ b/packages/wp-now/src/tests/wp-now.spec.ts @@ -747,8 +747,18 @@ describe('Test starting different modes', () => { const options = await getWpNowConfig({ blueprint: path.join(blueprintExamplesPath, 'wp-config.json'), }); - const { url, stopServer } = await startServer(options); + const { url, php, stopServer } = await startServer(options); expect(url).toMatch('http://myurl.wpnow'); + + php.writeFile( + `${php.documentRoot}/print-constants.php`, + ` Date: Fri, 23 Jun 2023 12:06:32 +0200 Subject: [PATCH 17/23] wp-now: add Blueprint documentation to README.md --- packages/wp-now/README.md | 48 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) diff --git a/packages/wp-now/README.md b/packages/wp-now/README.md index 28c1c39b..70fcbb18 100644 --- a/packages/wp-now/README.md +++ b/packages/wp-now/README.md @@ -57,6 +57,7 @@ wp-now php my-file.php - `--php=`: the version of PHP to use. This is optional and if not provided, it will use a default version which is `8.0`(example usage: `--php=7.4`); - `--port=`: the port number on which the server will listen. This is optional and if not provided, it will pick an open port number automatically. The default port number is set to `8881`(example of usage: `--port=3000`); - `--wp=`: the version of WordPress to use. This is optional and if not provided, it will use a default version. The default version is set to the [latest WordPress version](https://wordpress.org/download/releases/)(example usage: `--wp=5.8`) +- `--blueprint=`: the path of a JSON file with the Blueprint steps. This is optional, if provided will execute the defined Blueprints. See [Using Blueprints](#using-blueprints) for more details. Of these, `wp-now php` currently supports the `--path=` and `--php=` arguments. @@ -65,6 +66,53 @@ Of these, `wp-now php` currently supports the `--path=` and `--php= Date: Fri, 23 Jun 2023 13:09:53 +0200 Subject: [PATCH 18/23] wp-now: add Debugging steps using blueprints to README.md --- packages/wp-now/README.md | 41 ++++++++++++++++++++++++++++++++++++++- 1 file changed, 40 insertions(+), 1 deletion(-) diff --git a/packages/wp-now/README.md b/packages/wp-now/README.md index 70fcbb18..674a23f6 100644 --- a/packages/wp-now/README.md +++ b/packages/wp-now/README.md @@ -74,7 +74,7 @@ Blueprints are JSON files with a list of steps to execute after starting wp-now. Here is an example of a blueprint that defines custom URL constant. `wp-now` will automatically detect the blueprint and execute it after starting the server. In consequence, the site will be available at `http://myurl.wpnow`. Make sure myurl.wpnow is added to your hosts file. -To execute this blueprint, create a file named `blueprint-example.json` and run `wp-now start --blueprint=path/to/blueprint-example.json`. +To execute this blueprint, create a file named `blueprint-example.json` and run `wp-now start --blueprint=path/to/blueprint-example.json`. Note that the `virtualize` is set to `true` to avoid modifying the `wp-config.php` file that is shared between all the projects. ```json { @@ -113,6 +113,45 @@ The Blueprint to listen on port `80` will look like this: } ``` +## Debugging + +In the similar way we can define `WP_DEBUG` constants and read the debug logs. + +Run `wp-now start --blueprint=path/to/blueprint-example.json` where `blueprint-example.json` is: + +```json +{ + "steps": [ + { + "step": "defineWpConfigConsts", + "consts": { + "WP_DEBUG": true, + "WP_DEBUG_LOG": true + }, + "virtualize": true + } + ] +} +``` + +This will enable the debug logs and will create a `debug.log` file in the `~/wp-now/wp-content/${project}/debug.log` directory. +If you prefer to set a custom path for the debug log file, you can set `WP_DEBUG_LOG` to be a directory. Remember that the `php-wasm` server runs udner a VFS (virtual file system) where the default documentRoot is always `/var/www/html`. +For example, if you run `wp-now start --blueprint=path/to/blueprint-example.json` from a theme named `atlas` you could use this directory: `/var/www/html/wp-content/themes/atlas/example.log` and you will find the `example.log` file in your project directory. + +```json +{ + "steps": [ + { + "step": "defineWpConfigConsts", + "consts": { + "WP_DEBUG_LOG": "/var/www/html/wp-content/themes/atlas/example.log" + }, + "virtualize": true + } + ] +} +``` + ## Known Issues - Running `wp-now start` in 'wp-content' or 'wordpress' mode will produce some empty directories: [WordPress/wordpress-playground#328](https://github.com/WordPress/wordpress-playground/issues/328) From 598c570856f2d04b20aa73dc6d6687ff923a3e22 Mon Sep 17 00:00:00 2001 From: sejas Date: Fri, 23 Jun 2023 13:12:14 +0200 Subject: [PATCH 19/23] wp-now: remove unnecessary import --- packages/wp-now/src/wp-now.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/wp-now/src/wp-now.ts b/packages/wp-now/src/wp-now.ts index dd2967a3..d42a8e23 100644 --- a/packages/wp-now/src/wp-now.ts +++ b/packages/wp-now/src/wp-now.ts @@ -8,7 +8,6 @@ import { downloadWordPress, } from './download'; import { - Blueprint, StepDefinition, activatePlugin, activateTheme, From 56f2e44c72333bb9e712d05bcd3c76362c82618a Mon Sep 17 00:00:00 2001 From: sejas Date: Fri, 23 Jun 2023 13:52:12 +0200 Subject: [PATCH 20/23] wp-now: update blueprint tests to include WP_DEBUG_LOG and simplify WP_SITEURL --- packages/wp-now/src/tests/wp-now.spec.ts | 23 +++++++++++++++-------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/packages/wp-now/src/tests/wp-now.spec.ts b/packages/wp-now/src/tests/wp-now.spec.ts index de645f82..c770fb0d 100644 --- a/packages/wp-now/src/tests/wp-now.spec.ts +++ b/packages/wp-now/src/tests/wp-now.spec.ts @@ -743,24 +743,31 @@ describe('Test starting different modes', () => { */ describe('blueprints', () => { const blueprintExamplesPath = path.join(__dirname, 'blueprints'); - test('setting wp-config variables such as WP_SITEURL through blueprint', async () => { + + test('setting wp-config variable WP_DEBUG_LOG through blueprint', async () => { const options = await getWpNowConfig({ - blueprint: path.join(blueprintExamplesPath, 'wp-config.json'), + blueprint: path.join(blueprintExamplesPath, 'wp-debug.json'), }); - const { url, php, stopServer } = await startServer(options); - expect(url).toMatch('http://myurl.wpnow'); - + const { php, stopServer } = await startServer(options); php.writeFile( `${php.documentRoot}/print-constants.php`, - ` { + const options = await getWpNowConfig({ + blueprint: path.join(blueprintExamplesPath, 'wp-config.json'), + }); + expect(options.absoluteUrl).toMatch('http://myurl.wpnow'); + }); }); }); From e821a67287b16bfde641a4129c2321af716f025c Mon Sep 17 00:00:00 2001 From: sejas Date: Fri, 23 Jun 2023 13:53:23 +0200 Subject: [PATCH 21/23] code style format --- packages/wp-now/src/tests/wp-now.spec.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/wp-now/src/tests/wp-now.spec.ts b/packages/wp-now/src/tests/wp-now.spec.ts index c770fb0d..84d9ffcc 100644 --- a/packages/wp-now/src/tests/wp-now.spec.ts +++ b/packages/wp-now/src/tests/wp-now.spec.ts @@ -757,7 +757,9 @@ describe('Test starting different modes', () => { method: 'GET', url: '/print-constants.php', }); - expect(result.text).toMatch('/var/www/html/wp-content/themes/fake/example.log'); + expect(result.text).toMatch( + '/var/www/html/wp-content/themes/fake/example.log' + ); await stopServer(); }); From f42ddfdf1608b06bb070c67b00fd0fc1dac77a34 Mon Sep 17 00:00:00 2001 From: sejas Date: Fri, 23 Jun 2023 14:22:29 +0200 Subject: [PATCH 22/23] wp-now: update blueprint tests --- .../src/tests/blueprints/wp-config.json | 4 +- .../wp-now/src/tests/blueprints/wp-debug.json | 11 + packages/wp-now/src/tests/wp-now.spec.ts | 686 +----------------- 3 files changed, 34 insertions(+), 667 deletions(-) create mode 100644 packages/wp-now/src/tests/blueprints/wp-debug.json diff --git a/packages/wp-now/src/tests/blueprints/wp-config.json b/packages/wp-now/src/tests/blueprints/wp-config.json index ba93cf4f..05ae3d22 100644 --- a/packages/wp-now/src/tests/blueprints/wp-config.json +++ b/packages/wp-now/src/tests/blueprints/wp-config.json @@ -3,8 +3,8 @@ { "step": "defineWpConfigConsts", "consts": { - "WP_HOME": "http://myurl.wpnow", - "WP_SITEURL": "http://myurl.wpnow" + "WP_HOME": "http://127.0.0.1", + "WP_SITEURL": "http://127.0.0.1" }, "virtualize": true } diff --git a/packages/wp-now/src/tests/blueprints/wp-debug.json b/packages/wp-now/src/tests/blueprints/wp-debug.json new file mode 100644 index 00000000..f189ee3a --- /dev/null +++ b/packages/wp-now/src/tests/blueprints/wp-debug.json @@ -0,0 +1,11 @@ +{ + "steps": [ + { + "step": "defineWpConfigConsts", + "consts": { + "WP_DEBUG_LOG": "/var/www/html/wp-content/themes/fake/example.log" + }, + "virtualize": true + } + ] +} diff --git a/packages/wp-now/src/tests/wp-now.spec.ts b/packages/wp-now/src/tests/wp-now.spec.ts index 84d9ffcc..3f9a0069 100644 --- a/packages/wp-now/src/tests/wp-now.spec.ts +++ b/packages/wp-now/src/tests/wp-now.spec.ts @@ -1,6 +1,6 @@ import startWPNow, { getThemeTemplate, inferMode } from '../wp-now'; import { startServer } from '../'; -import getWpNowConfig, { CliOptions, WPNowMode } from '../config'; +import getWpNowConfig, { CliOptions, WPNowMode, WPNowOptions } from '../config'; import fs from 'fs-extra'; import path from 'path'; import { @@ -32,168 +32,6 @@ async function downloadWithTimer(name, fn) { console.timeEnd(name); } -// Options -test('getWpNowConfig with default options', async () => { - const projectDirectory = exampleDir + '/index'; - const rawOptions: CliOptions = { - path: projectDirectory, - }; - const options = await getWpNowConfig(rawOptions); - - expect(options.phpVersion).toBe('8.0'); - expect(options.wordPressVersion).toBe('latest'); - expect(options.documentRoot).toBe('/var/www/html'); - expect(options.mode).toBe(WPNowMode.INDEX); - expect(options.projectPath).toBe(projectDirectory); -}); - -//TODO: Add it back when all options are supported as cli arguments -// test('parseOptions with custom options', async () => { -// const rawOptions: Partial = { -// phpVersion: '7.3', -// wordPressVersion: '5.7', -// documentRoot: '/var/www/my-site', -// mode: WPNowMode.WORDPRESS, -// projectPath: '/path/to/my-site', -// wpContentPath: '/path/to/my-site/wp-content', -// absoluteUrl: 'http://localhost:8080', -// }; -// const options = await parseOptions(rawOptions); -// expect(options.phpVersion).toBe('7.3'); -// expect(options.wordPressVersion).toBe('5.7'); -// expect(options.documentRoot).toBe('/var/www/my-site'); -// expect(options.mode).toBe(WPNowMode.WORDPRESS); -// expect(options.projectPath).toBe('/path/to/my-site'); -// expect(options.wpContentPath).toBe('/path/to/my-site/wp-content'); -// expect(options.absoluteUrl).toBe('http://localhost:8080'); -// }); - -test('getWpNowConfig with unsupported PHP version', async () => { - const rawOptions: CliOptions = { - php: '5.4' as any, - }; - await expect(getWpNowConfig(rawOptions)).rejects.toThrowError( - 'Unsupported PHP version: 5.4.' - ); -}); - -// Plugin mode -test('isPluginDirectory detects a WordPress plugin and infer PLUGIN mode.', () => { - const projectPath = exampleDir + '/plugin'; - expect(isPluginDirectory(projectPath)).toBe(true); - expect(inferMode(projectPath)).toBe(WPNowMode.PLUGIN); -}); - -test('isPluginDirectory detects a WordPress plugin in case-insensitive way and infer PLUGIN mode.', () => { - const projectPath = exampleDir + '/plugin-case-insensitive'; - expect(isPluginDirectory(projectPath)).toBe(true); - expect(inferMode(projectPath)).toBe(WPNowMode.PLUGIN); -}); - -test('isPluginDirectory returns false for non-plugin directory', () => { - const projectPath = exampleDir + '/not-plugin'; - expect(isPluginDirectory(projectPath)).toBe(false); - expect(inferMode(projectPath)).toBe(WPNowMode.PLAYGROUND); -}); - -// Theme mode -test('isThemeDirectory detects a WordPress theme and infer THEME mode', () => { - const projectPath = exampleDir + '/theme'; - expect(isThemeDirectory(projectPath)).toBe(true); - expect(inferMode(projectPath)).toBe(WPNowMode.THEME); -}); - -test('getThemeTemplate detects if a WordPress theme has a parent template.', () => { - const projectPath = exampleDir + '/child-theme/child'; - expect(getThemeTemplate(projectPath)).toBe('parent'); - expect(inferMode(projectPath)).toBe(WPNowMode.THEME); -}); - -test('getThemeTemplate returns falsy value for themes without parent', () => { - const projectPath = exampleDir + '/theme'; - expect(getThemeTemplate(projectPath)).toBe(undefined); - expect(inferMode(projectPath)).toBe(WPNowMode.THEME); -}); - -test('isThemeDirectory returns false for non-theme directory', () => { - const projectPath = exampleDir + '/not-theme'; - expect(isThemeDirectory(projectPath)).toBe(false); - expect(inferMode(projectPath)).toBe(WPNowMode.PLAYGROUND); -}); - -test('isThemeDirectory returns false for a directory with style.css but without Theme Name', () => { - const projectPath = exampleDir + '/not-theme'; - - expect(isThemeDirectory(projectPath)).toBe(false); - expect(inferMode(projectPath)).toBe(WPNowMode.PLAYGROUND); -}); - -// WP_CONTENT mode -test('isWpContentDirectory detects a WordPress wp-content directory and infer WP_CONTENT mode', () => { - const projectPath = exampleDir + '/wp-content'; - expect(isWpContentDirectory(projectPath)).toBe(true); - expect(isWordPressDirectory(projectPath)).toBe(false); - expect(inferMode(projectPath)).toBe(WPNowMode.WP_CONTENT); -}); - -test('isWpContentDirectory returns false for wp-content parent directory', () => { - const projectPath = exampleDir + '/index'; - expect(isWpContentDirectory(projectPath)).toBe(false); - expect(isWordPressDirectory(projectPath)).toBe(false); - expect(inferMode(projectPath)).toBe(WPNowMode.INDEX); -}); - -test('isWpContentDirectory returns true for a directory with only themes directory', () => { - const projectPath = exampleDir + '/wp-content-only-themes'; - expect(isWpContentDirectory(projectPath)).toBe(true); - expect(isWordPressDirectory(projectPath)).toBe(false); - expect(inferMode(projectPath)).toBe(WPNowMode.WP_CONTENT); -}); - -test('isWpContentDirectory returns true for a directory with only mu-plugins directory', () => { - const projectPath = exampleDir + '/wp-content-only-mu-plugins'; - expect(isWpContentDirectory(projectPath)).toBe(true); - expect(isWordPressDirectory(projectPath)).toBe(false); - expect(inferMode(projectPath)).toBe(WPNowMode.WP_CONTENT); -}); - -// WordPress mode -test('isWordPressDirectory detects a WordPress directory and WORDPRESS mode', () => { - const projectPath = exampleDir + '/wordpress'; - - expect(isWordPressDirectory(projectPath)).toBe(true); - expect(inferMode(projectPath)).toBe(WPNowMode.WORDPRESS); -}); - -test('isWordPressDirectory returns false for non-WordPress directory', () => { - const projectPath = exampleDir + '/nothing'; - - expect(isWordPressDirectory(projectPath)).toBe(false); - expect(inferMode(projectPath)).toBe(WPNowMode.PLAYGROUND); -}); - -// WordPress developer mode -test('isWordPressDevelopDirectory detects a WordPress-develop directory and WORDPRESS_DEVELOP mode', () => { - const projectPath = exampleDir + '/wordpress-develop'; - - expect(isWordPressDevelopDirectory(projectPath)).toBe(true); - expect(inferMode(projectPath)).toBe(WPNowMode.WORDPRESS_DEVELOP); -}); - -test('isWordPressDevelopDirectory returns false for non-WordPress-develop directory', () => { - const projectPath = exampleDir + '/nothing'; - - expect(isWordPressDevelopDirectory(projectPath)).toBe(false); - expect(inferMode(projectPath)).toBe(WPNowMode.PLAYGROUND); -}); - -test('isWordPressDevelopDirectory returns false for incomplete WordPress-develop directory', () => { - const projectPath = exampleDir + '/not-wordpress-develop'; - - expect(isWordPressDevelopDirectory(projectPath)).toBe(false); - expect(inferMode(projectPath)).toBe(WPNowMode.PLAYGROUND); -}); - describe('Test starting different modes', () => { let tmpExampleDirectory; @@ -238,512 +76,19 @@ describe('Test starting different modes', () => { }); /** - * Expect that all provided mount point paths are empty directories which are result of file system mounts. - * - * @param mountPaths List of mount point paths that should exist on file system. - * @param projectPath Project path. - */ - const expectEmptyMountPoints = (mountPaths, projectPath) => { - mountPaths.map((relativePath) => { - const fullPath = path.join(projectPath, relativePath); - - expect({ - path: fullPath, - exists: fs.existsSync(fullPath), - }).toStrictEqual({ path: fullPath, exists: true }); - - expect({ - path: fullPath, - content: fs.readdirSync(fullPath), - }).toStrictEqual({ path: fullPath, content: [] }); - - expect({ - path: fullPath, - isDirectory: fs.lstatSync(fullPath).isDirectory(), - }).toStrictEqual({ path: fullPath, isDirectory: true }); - }); - }; - - /** - * Expect that all listed files do not exist in project directory - * - * @param forbiddenFiles List of files that should not exist on file system. - * @param projectPath Project path. - */ - const expectForbiddenProjectFiles = (forbiddenFiles, projectPath) => { - forbiddenFiles.map((relativePath) => { - const fullPath = path.join(projectPath, relativePath); - expect({ - path: fullPath, - exists: fs.existsSync(fullPath), - }).toStrictEqual({ path: fullPath, exists: false }); - }); - }; - - /** - * Expect that all required files exist for PHP. - * - * @param requiredFiles List of files that should be accessible by PHP. - * @param documentRoot Document root of the PHP server. - * @param php NodePHP instance. - */ - const expectRequiredRootFiles = (requiredFiles, documentRoot, php) => { - requiredFiles.map((relativePath) => { - const fullPath = path.join(documentRoot, relativePath); - expect({ - path: fullPath, - exists: php.fileExists(fullPath), - }).toStrictEqual({ path: fullPath, exists: true }); - }); - }; - - /** - * Test that startWPNow in "index", "plugin", "theme", and "playground" modes doesn't change anything in the project directory. - */ - test.each([ - ['index', ['index.php']], - ['plugin', ['sample-plugin.php']], - ['theme', ['style.css']], - ['playground', ['my-file.txt']], - ])('startWPNow starts %s mode', async (mode, expectedDirectories) => { - const projectPath = path.join(tmpExampleDirectory, mode); - - const rawOptions: CliOptions = { - path: projectPath, - }; - - const options = await getWpNowConfig(rawOptions); - - await startWPNow(options); - - const forbiddenPaths = ['wp-config.php']; - - expectForbiddenProjectFiles(forbiddenPaths, projectPath); - - expect(fs.readdirSync(projectPath)).toEqual(expectedDirectories); - }); - - /** - * Test that startWPNow in "wp-content" mode mounts required files and directories, and - * that required files exist for PHP. - */ - test('startWPNow starts wp-content mode', async () => { - const projectPath = path.join(tmpExampleDirectory, 'wp-content'); - - const rawOptions: CliOptions = { - path: projectPath, - }; - - const options = await getWpNowConfig(rawOptions); - - const { php, options: wpNowOptions } = await startWPNow(options); - - const mountPointPaths = [ - 'database', - 'db.php', - 'mu-plugins', - 'plugins/sqlite-database-integration', - ]; - - expectEmptyMountPoints(mountPointPaths, projectPath); - - const forbiddenPaths = ['wp-config.php']; - - expectForbiddenProjectFiles(forbiddenPaths, projectPath); - - const requiredFiles = [ - 'wp-content/db.php', - 'wp-content/mu-plugins/0-allow-wp-org.php', - ]; - - expectRequiredRootFiles(requiredFiles, wpNowOptions.documentRoot, php); - }); - - /** - * Test that startWPNow in "wordpress" mode without existing wp-config.php file mounts - * required files and directories, and that required files exist for PHP. - */ - test('startWPNow starts wordpress mode', async () => { - const projectPath = path.join(tmpExampleDirectory, 'wordpress'); - - const rawOptions: CliOptions = { - path: projectPath, - }; - const options = await getWpNowConfig(rawOptions); - - const { php, options: wpNowOptions } = await startWPNow(options); - - const mountPointPaths = [ - 'wp-content/database', - 'wp-content/db.php', - 'wp-content/mu-plugins', - 'wp-content/plugins/sqlite-database-integration', - ]; - - expectEmptyMountPoints(mountPointPaths, projectPath); - - const requiredFiles = [ - 'wp-content/db.php', - 'wp-content/mu-plugins/0-allow-wp-org.php', - 'wp-config.php', - ]; - - expectRequiredRootFiles(requiredFiles, wpNowOptions.documentRoot, php); - }); - - /** - * Test that startWPNow in "wordpress" mode with existing wp-config.php file mounts - * required files and directories, and that required files exist for PHP. - */ - test('startWPNow starts wordpress mode with existing wp-config', async () => { - const projectPath = path.join( - tmpExampleDirectory, - 'wordpress-with-config' - ); - - const rawOptions: CliOptions = { - path: projectPath, - }; - const options = await getWpNowConfig(rawOptions); - - const { php, options: wpNowOptions } = await startWPNow(options); - - const mountPointPaths = ['wp-content/mu-plugins']; - - expectEmptyMountPoints(mountPointPaths, projectPath); - - const forbiddenPaths = [ - 'wp-content/database', - 'wp-content/db.php', - 'wp-content/plugins/sqlite-database-integration', - ]; - - expectForbiddenProjectFiles(forbiddenPaths, projectPath); - - const requiredFiles = [ - 'wp-content/mu-plugins/0-allow-wp-org.php', - 'wp-config.php', - ]; - - expectRequiredRootFiles(requiredFiles, wpNowOptions.documentRoot, php); - }); - - /** - * Test that startWPNow in "plugin" mode auto installs the plugin. - */ - test('startWPNow auto installs the plugin', async () => { - const projectPath = path.join(tmpExampleDirectory, 'plugin'); - const options = await getWpNowConfig({ path: projectPath }); - const { php } = await startWPNow(options); - const codeIsPluginActivePhp = ` { - const projectPath = path.join(tmpExampleDirectory, 'plugin'); - const options = await getWpNowConfig({ path: projectPath }); - const { php } = await startWPNow(options); - const deactivatePluginPhp = ` { - const projectPath = path.join(tmpExampleDirectory, 'theme'); - const options = await getWpNowConfig({ path: projectPath }); - const { php } = await startWPNow(options); - const codeActiveThemeNamePhp = `get('Name'); - `; - const themeName = await php.run({ - code: codeActiveThemeNamePhp, - }); - - expect(themeName.text).toContain('Yolo Theme'); - }); - - /** - * Test that startWPNow in "theme" mode auto activates the theme. - */ - test('startWPNow auto installs mounts child and parent theme.', async () => { - const projectPath = path.join(tmpExampleDirectory, 'child-theme/child'); - const options = await getWpNowConfig({ path: projectPath }); - const { php } = await startWPNow(options); - - const childThemePhp = `get('Name'); - `; - - const parentThemePhp = `exists() ) { - echo $theme->get('Name'); - } else { - echo 'Not found'; - } - `; - - const childThemeName = await php.run({ - code: childThemePhp, - }); - const parentThemeName = await php.run({ - code: parentThemePhp, - }); - - expect(childThemeName.text).toContain('Child Theme'); - expect(parentThemeName.text).toContain('Parent Theme'); - }); - - /** - * Test that startWPNow in "theme" mode auto activates the theme. - */ - test('startWPNow auto handles child theme with missing parent.', async () => { - const projectPath = path.join( - tmpExampleDirectory, - 'child-theme-missing-parent/child' - ); - const options = await getWpNowConfig({ path: projectPath }); - const { php } = await startWPNow(options); - - const childThemePhp = `get('Name'); - `; - - const childThemeName = await php.run({ - code: childThemePhp, - }); - - expect(childThemeName.text).toContain('Child Theme'); - }); - - /** - * Test that startWPNow in "theme" mode does not auto activate the theme the second time. - */ - test('startWPNow auto installs the theme', async () => { - const projectPath = path.join(tmpExampleDirectory, 'theme'); - const options = await getWpNowConfig({ path: projectPath }); - const { php } = await startWPNow(options); - const switchThemePhp = `get('Name'); - `; - const themeName = await phpSecondTime.run({ - code: codeActiveThemeNamePhp, - }); - - expect(themeName.text).toContain('Twenty Twenty-Three'); - }); - - /* - * Test that startWPNow doesn't leave dirty files. - * - * @see https://github.com/WordPress/playground-tools/issues/32 - */ - test.skip('startWPNow does not leave dirty folders and files', async () => { - const projectPath = path.join(tmpExampleDirectory, 'wordpress'); - const options = await getWpNowConfig({ path: projectPath }); - await startWPNow(options); - expect( - fs.existsSync( - path.join( - projectPath, - 'wp-content', - 'mu-plugins', - '0-allow-wp-org.php' - ) - ) - ).toBe(false); - expect( - fs.existsSync(path.join(projectPath, 'wp-content', 'database')) - ).toBe(false); - expect( - fs.existsSync( - path.join( - projectPath, - 'wp-content', - 'plugins', - 'sqlite-database-integration' - ) - ) - ).toBe(false); - expect( - fs.existsSync(path.join(projectPath, 'wp-content', 'db.php')) - ).toBe(false); - }); - - /** - * Test PHP integration test executing runCli. + * Test blueprints execution. */ - describe('validate php comand arguments passed through yargs', () => { - const phpExampleDir = path.join(__dirname, 'execute-php'); - const argv = process.argv; - let output = ''; - let processExitMock; - beforeEach(() => { - vi.spyOn(console, 'log').mockImplementation((newLine: string) => { - output += `${newLine}\n`; - }); - processExitMock = vi - .spyOn(process, 'exit') - .mockImplementation(() => null); - }); + describe('blueprints', () => { + const blueprintExamplesPath = path.join(__dirname, 'blueprints'); afterEach(() => { - output = ''; - process.argv = argv; - vi.resetAllMocks(); - }); - - test('php should receive the correct yargs arguments', async () => { - process.argv = ['node', 'wp-now', 'php', '--', '--version']; - await runCli(); - expect(output).toMatch(/PHP 8\.0(.*)\(cli\)/i); - expect(processExitMock).toHaveBeenCalledWith(0); - }); - - test('wp-now should change the php version', async () => { - process.argv = [ - 'node', - 'wp-now', - 'php', - '--php=7.4', - '--', - '--version', - ]; - await runCli(); - expect(output).toMatch(/PHP 7\.4(.*)\(cli\)/i); - expect(processExitMock).toHaveBeenCalledWith(0); - }); - - test('php should execute a file', async () => { - const filePath = path.join(phpExampleDir, 'php-version.php'); - process.argv = ['node', 'wp-now', 'php', filePath]; - await runCli(); - expect(output).toMatch(/8\.0/i); - expect(processExitMock).toHaveBeenCalledWith(0); - }); - - test('php should execute a file and change php version', async () => { - const filePath = path.join(phpExampleDir, 'php-version.php'); - process.argv = [ - 'node', - 'wp-now', - 'php', - '--php=7.4', - '--', - filePath, - ]; - await runCli(); - expect(output).toMatch(/7\.4/i); - expect(processExitMock).toHaveBeenCalledWith(0); - }); - }); - - /** - * Test startServer. - */ - describe('startServer', () => { - let stopServer, options; - - beforeEach(async () => { - const projectPath = path.join( - tmpExampleDirectory, - 'theme-with-assets' + // Clean the custom url from the SQLite database + fs.rmSync( + path.join(getWpNowTmpPath(), 'wp-content', 'playground'), + { recursive: true } ); - const config = await getWpNowConfig({ path: projectPath }); - const server = await startServer(config); - stopServer = server.stopServer; - options = server.options; }); - afterEach(async () => { - options = null; - await stopServer(); - }); - - /** - * Test that startServer compresses the text files correctly. - */ - test.each([ - ['html', ''], - ['css', '/wp-content/themes/theme-with-assets/style.css'], - [ - 'javascript', - '/wp-content/themes/theme-with-assets/javascript.js', - ], - ])('startServer compresses the %s file', async (_, file) => { - const req = await fetch(`${options.absoluteUrl}${file}`); - expect(req.headers.get('content-encoding')).toBe('gzip'); - }); - - /** - * Test that startServer doesn't compress non text files. - */ - test.each([ - ['png', '/wp-content/themes/theme-with-assets/image.png'], - ['jpg', '/wp-content/themes/theme-with-assets/image.jpg'], - ])("startServer doesn't compress the %s file", async (_, file) => { - const req = await fetch(`${options.absoluteUrl}${file}`); - expect(req.headers.get('content-encoding')).toBe(null); - }); - }); - - /** - * Test blueprints execution. - */ - describe('blueprints', () => { - const blueprintExamplesPath = path.join(__dirname, 'blueprints'); - test('setting wp-config variable WP_DEBUG_LOG through blueprint', async () => { const options = await getWpNowConfig({ blueprint: path.join(blueprintExamplesPath, 'wp-debug.json'), @@ -763,12 +108,23 @@ describe('Test starting different modes', () => { await stopServer(); }); - // This must be the last blueprint test, because it changes the siteurl and the DNS won't resolve that custom URL. test('setting wp-config variable WP_SITEURL through blueprint', async () => { const options = await getWpNowConfig({ blueprint: path.join(blueprintExamplesPath, 'wp-config.json'), }); - expect(options.absoluteUrl).toMatch('http://myurl.wpnow'); + const { php, stopServer } = await startServer(options); + expect(options.absoluteUrl).toBe('http://127.0.0.1'); + + php.writeFile( + `${php.documentRoot}/print-constants.php`, + ` Date: Fri, 23 Jun 2023 14:32:04 +0200 Subject: [PATCH 23/23] wp-now: undo restore removed tests --- packages/wp-now/src/tests/wp-now.spec.ts | 665 ++++++++++++++++++++++- 1 file changed, 664 insertions(+), 1 deletion(-) diff --git a/packages/wp-now/src/tests/wp-now.spec.ts b/packages/wp-now/src/tests/wp-now.spec.ts index 3f9a0069..41babc2b 100644 --- a/packages/wp-now/src/tests/wp-now.spec.ts +++ b/packages/wp-now/src/tests/wp-now.spec.ts @@ -1,6 +1,6 @@ import startWPNow, { getThemeTemplate, inferMode } from '../wp-now'; import { startServer } from '../'; -import getWpNowConfig, { CliOptions, WPNowMode, WPNowOptions } from '../config'; +import getWpNowConfig, { CliOptions, WPNowMode } from '../config'; import fs from 'fs-extra'; import path from 'path'; import { @@ -32,6 +32,168 @@ async function downloadWithTimer(name, fn) { console.timeEnd(name); } +// Options +test('getWpNowConfig with default options', async () => { + const projectDirectory = exampleDir + '/index'; + const rawOptions: CliOptions = { + path: projectDirectory, + }; + const options = await getWpNowConfig(rawOptions); + + expect(options.phpVersion).toBe('8.0'); + expect(options.wordPressVersion).toBe('latest'); + expect(options.documentRoot).toBe('/var/www/html'); + expect(options.mode).toBe(WPNowMode.INDEX); + expect(options.projectPath).toBe(projectDirectory); +}); + +//TODO: Add it back when all options are supported as cli arguments +// test('parseOptions with custom options', async () => { +// const rawOptions: Partial = { +// phpVersion: '7.3', +// wordPressVersion: '5.7', +// documentRoot: '/var/www/my-site', +// mode: WPNowMode.WORDPRESS, +// projectPath: '/path/to/my-site', +// wpContentPath: '/path/to/my-site/wp-content', +// absoluteUrl: 'http://localhost:8080', +// }; +// const options = await parseOptions(rawOptions); +// expect(options.phpVersion).toBe('7.3'); +// expect(options.wordPressVersion).toBe('5.7'); +// expect(options.documentRoot).toBe('/var/www/my-site'); +// expect(options.mode).toBe(WPNowMode.WORDPRESS); +// expect(options.projectPath).toBe('/path/to/my-site'); +// expect(options.wpContentPath).toBe('/path/to/my-site/wp-content'); +// expect(options.absoluteUrl).toBe('http://localhost:8080'); +// }); + +test('getWpNowConfig with unsupported PHP version', async () => { + const rawOptions: CliOptions = { + php: '5.4' as any, + }; + await expect(getWpNowConfig(rawOptions)).rejects.toThrowError( + 'Unsupported PHP version: 5.4.' + ); +}); + +// Plugin mode +test('isPluginDirectory detects a WordPress plugin and infer PLUGIN mode.', () => { + const projectPath = exampleDir + '/plugin'; + expect(isPluginDirectory(projectPath)).toBe(true); + expect(inferMode(projectPath)).toBe(WPNowMode.PLUGIN); +}); + +test('isPluginDirectory detects a WordPress plugin in case-insensitive way and infer PLUGIN mode.', () => { + const projectPath = exampleDir + '/plugin-case-insensitive'; + expect(isPluginDirectory(projectPath)).toBe(true); + expect(inferMode(projectPath)).toBe(WPNowMode.PLUGIN); +}); + +test('isPluginDirectory returns false for non-plugin directory', () => { + const projectPath = exampleDir + '/not-plugin'; + expect(isPluginDirectory(projectPath)).toBe(false); + expect(inferMode(projectPath)).toBe(WPNowMode.PLAYGROUND); +}); + +// Theme mode +test('isThemeDirectory detects a WordPress theme and infer THEME mode', () => { + const projectPath = exampleDir + '/theme'; + expect(isThemeDirectory(projectPath)).toBe(true); + expect(inferMode(projectPath)).toBe(WPNowMode.THEME); +}); + +test('getThemeTemplate detects if a WordPress theme has a parent template.', () => { + const projectPath = exampleDir + '/child-theme/child'; + expect(getThemeTemplate(projectPath)).toBe('parent'); + expect(inferMode(projectPath)).toBe(WPNowMode.THEME); +}); + +test('getThemeTemplate returns falsy value for themes without parent', () => { + const projectPath = exampleDir + '/theme'; + expect(getThemeTemplate(projectPath)).toBe(undefined); + expect(inferMode(projectPath)).toBe(WPNowMode.THEME); +}); + +test('isThemeDirectory returns false for non-theme directory', () => { + const projectPath = exampleDir + '/not-theme'; + expect(isThemeDirectory(projectPath)).toBe(false); + expect(inferMode(projectPath)).toBe(WPNowMode.PLAYGROUND); +}); + +test('isThemeDirectory returns false for a directory with style.css but without Theme Name', () => { + const projectPath = exampleDir + '/not-theme'; + + expect(isThemeDirectory(projectPath)).toBe(false); + expect(inferMode(projectPath)).toBe(WPNowMode.PLAYGROUND); +}); + +// WP_CONTENT mode +test('isWpContentDirectory detects a WordPress wp-content directory and infer WP_CONTENT mode', () => { + const projectPath = exampleDir + '/wp-content'; + expect(isWpContentDirectory(projectPath)).toBe(true); + expect(isWordPressDirectory(projectPath)).toBe(false); + expect(inferMode(projectPath)).toBe(WPNowMode.WP_CONTENT); +}); + +test('isWpContentDirectory returns false for wp-content parent directory', () => { + const projectPath = exampleDir + '/index'; + expect(isWpContentDirectory(projectPath)).toBe(false); + expect(isWordPressDirectory(projectPath)).toBe(false); + expect(inferMode(projectPath)).toBe(WPNowMode.INDEX); +}); + +test('isWpContentDirectory returns true for a directory with only themes directory', () => { + const projectPath = exampleDir + '/wp-content-only-themes'; + expect(isWpContentDirectory(projectPath)).toBe(true); + expect(isWordPressDirectory(projectPath)).toBe(false); + expect(inferMode(projectPath)).toBe(WPNowMode.WP_CONTENT); +}); + +test('isWpContentDirectory returns true for a directory with only mu-plugins directory', () => { + const projectPath = exampleDir + '/wp-content-only-mu-plugins'; + expect(isWpContentDirectory(projectPath)).toBe(true); + expect(isWordPressDirectory(projectPath)).toBe(false); + expect(inferMode(projectPath)).toBe(WPNowMode.WP_CONTENT); +}); + +// WordPress mode +test('isWordPressDirectory detects a WordPress directory and WORDPRESS mode', () => { + const projectPath = exampleDir + '/wordpress'; + + expect(isWordPressDirectory(projectPath)).toBe(true); + expect(inferMode(projectPath)).toBe(WPNowMode.WORDPRESS); +}); + +test('isWordPressDirectory returns false for non-WordPress directory', () => { + const projectPath = exampleDir + '/nothing'; + + expect(isWordPressDirectory(projectPath)).toBe(false); + expect(inferMode(projectPath)).toBe(WPNowMode.PLAYGROUND); +}); + +// WordPress developer mode +test('isWordPressDevelopDirectory detects a WordPress-develop directory and WORDPRESS_DEVELOP mode', () => { + const projectPath = exampleDir + '/wordpress-develop'; + + expect(isWordPressDevelopDirectory(projectPath)).toBe(true); + expect(inferMode(projectPath)).toBe(WPNowMode.WORDPRESS_DEVELOP); +}); + +test('isWordPressDevelopDirectory returns false for non-WordPress-develop directory', () => { + const projectPath = exampleDir + '/nothing'; + + expect(isWordPressDevelopDirectory(projectPath)).toBe(false); + expect(inferMode(projectPath)).toBe(WPNowMode.PLAYGROUND); +}); + +test('isWordPressDevelopDirectory returns false for incomplete WordPress-develop directory', () => { + const projectPath = exampleDir + '/not-wordpress-develop'; + + expect(isWordPressDevelopDirectory(projectPath)).toBe(false); + expect(inferMode(projectPath)).toBe(WPNowMode.PLAYGROUND); +}); + describe('Test starting different modes', () => { let tmpExampleDirectory; @@ -75,6 +237,507 @@ describe('Test starting different modes', () => { fs.rmSync(getWpNowTmpPath(), { recursive: true, force: true }); }); + /** + * Expect that all provided mount point paths are empty directories which are result of file system mounts. + * + * @param mountPaths List of mount point paths that should exist on file system. + * @param projectPath Project path. + */ + const expectEmptyMountPoints = (mountPaths, projectPath) => { + mountPaths.map((relativePath) => { + const fullPath = path.join(projectPath, relativePath); + + expect({ + path: fullPath, + exists: fs.existsSync(fullPath), + }).toStrictEqual({ path: fullPath, exists: true }); + + expect({ + path: fullPath, + content: fs.readdirSync(fullPath), + }).toStrictEqual({ path: fullPath, content: [] }); + + expect({ + path: fullPath, + isDirectory: fs.lstatSync(fullPath).isDirectory(), + }).toStrictEqual({ path: fullPath, isDirectory: true }); + }); + }; + + /** + * Expect that all listed files do not exist in project directory + * + * @param forbiddenFiles List of files that should not exist on file system. + * @param projectPath Project path. + */ + const expectForbiddenProjectFiles = (forbiddenFiles, projectPath) => { + forbiddenFiles.map((relativePath) => { + const fullPath = path.join(projectPath, relativePath); + expect({ + path: fullPath, + exists: fs.existsSync(fullPath), + }).toStrictEqual({ path: fullPath, exists: false }); + }); + }; + + /** + * Expect that all required files exist for PHP. + * + * @param requiredFiles List of files that should be accessible by PHP. + * @param documentRoot Document root of the PHP server. + * @param php NodePHP instance. + */ + const expectRequiredRootFiles = (requiredFiles, documentRoot, php) => { + requiredFiles.map((relativePath) => { + const fullPath = path.join(documentRoot, relativePath); + expect({ + path: fullPath, + exists: php.fileExists(fullPath), + }).toStrictEqual({ path: fullPath, exists: true }); + }); + }; + + /** + * Test that startWPNow in "index", "plugin", "theme", and "playground" modes doesn't change anything in the project directory. + */ + test.each([ + ['index', ['index.php']], + ['plugin', ['sample-plugin.php']], + ['theme', ['style.css']], + ['playground', ['my-file.txt']], + ])('startWPNow starts %s mode', async (mode, expectedDirectories) => { + const projectPath = path.join(tmpExampleDirectory, mode); + + const rawOptions: CliOptions = { + path: projectPath, + }; + + const options = await getWpNowConfig(rawOptions); + + await startWPNow(options); + + const forbiddenPaths = ['wp-config.php']; + + expectForbiddenProjectFiles(forbiddenPaths, projectPath); + + expect(fs.readdirSync(projectPath)).toEqual(expectedDirectories); + }); + + /** + * Test that startWPNow in "wp-content" mode mounts required files and directories, and + * that required files exist for PHP. + */ + test('startWPNow starts wp-content mode', async () => { + const projectPath = path.join(tmpExampleDirectory, 'wp-content'); + + const rawOptions: CliOptions = { + path: projectPath, + }; + + const options = await getWpNowConfig(rawOptions); + + const { php, options: wpNowOptions } = await startWPNow(options); + + const mountPointPaths = [ + 'database', + 'db.php', + 'mu-plugins', + 'plugins/sqlite-database-integration', + ]; + + expectEmptyMountPoints(mountPointPaths, projectPath); + + const forbiddenPaths = ['wp-config.php']; + + expectForbiddenProjectFiles(forbiddenPaths, projectPath); + + const requiredFiles = [ + 'wp-content/db.php', + 'wp-content/mu-plugins/0-allow-wp-org.php', + ]; + + expectRequiredRootFiles(requiredFiles, wpNowOptions.documentRoot, php); + }); + + /** + * Test that startWPNow in "wordpress" mode without existing wp-config.php file mounts + * required files and directories, and that required files exist for PHP. + */ + test('startWPNow starts wordpress mode', async () => { + const projectPath = path.join(tmpExampleDirectory, 'wordpress'); + + const rawOptions: CliOptions = { + path: projectPath, + }; + const options = await getWpNowConfig(rawOptions); + + const { php, options: wpNowOptions } = await startWPNow(options); + + const mountPointPaths = [ + 'wp-content/database', + 'wp-content/db.php', + 'wp-content/mu-plugins', + 'wp-content/plugins/sqlite-database-integration', + ]; + + expectEmptyMountPoints(mountPointPaths, projectPath); + + const requiredFiles = [ + 'wp-content/db.php', + 'wp-content/mu-plugins/0-allow-wp-org.php', + 'wp-config.php', + ]; + + expectRequiredRootFiles(requiredFiles, wpNowOptions.documentRoot, php); + }); + + /** + * Test that startWPNow in "wordpress" mode with existing wp-config.php file mounts + * required files and directories, and that required files exist for PHP. + */ + test('startWPNow starts wordpress mode with existing wp-config', async () => { + const projectPath = path.join( + tmpExampleDirectory, + 'wordpress-with-config' + ); + + const rawOptions: CliOptions = { + path: projectPath, + }; + const options = await getWpNowConfig(rawOptions); + + const { php, options: wpNowOptions } = await startWPNow(options); + + const mountPointPaths = ['wp-content/mu-plugins']; + + expectEmptyMountPoints(mountPointPaths, projectPath); + + const forbiddenPaths = [ + 'wp-content/database', + 'wp-content/db.php', + 'wp-content/plugins/sqlite-database-integration', + ]; + + expectForbiddenProjectFiles(forbiddenPaths, projectPath); + + const requiredFiles = [ + 'wp-content/mu-plugins/0-allow-wp-org.php', + 'wp-config.php', + ]; + + expectRequiredRootFiles(requiredFiles, wpNowOptions.documentRoot, php); + }); + + /** + * Test that startWPNow in "plugin" mode auto installs the plugin. + */ + test('startWPNow auto installs the plugin', async () => { + const projectPath = path.join(tmpExampleDirectory, 'plugin'); + const options = await getWpNowConfig({ path: projectPath }); + const { php } = await startWPNow(options); + const codeIsPluginActivePhp = ` { + const projectPath = path.join(tmpExampleDirectory, 'plugin'); + const options = await getWpNowConfig({ path: projectPath }); + const { php } = await startWPNow(options); + const deactivatePluginPhp = ` { + const projectPath = path.join(tmpExampleDirectory, 'theme'); + const options = await getWpNowConfig({ path: projectPath }); + const { php } = await startWPNow(options); + const codeActiveThemeNamePhp = `get('Name'); + `; + const themeName = await php.run({ + code: codeActiveThemeNamePhp, + }); + + expect(themeName.text).toContain('Yolo Theme'); + }); + + /** + * Test that startWPNow in "theme" mode auto activates the theme. + */ + test('startWPNow auto installs mounts child and parent theme.', async () => { + const projectPath = path.join(tmpExampleDirectory, 'child-theme/child'); + const options = await getWpNowConfig({ path: projectPath }); + const { php } = await startWPNow(options); + + const childThemePhp = `get('Name'); + `; + + const parentThemePhp = `exists() ) { + echo $theme->get('Name'); + } else { + echo 'Not found'; + } + `; + + const childThemeName = await php.run({ + code: childThemePhp, + }); + const parentThemeName = await php.run({ + code: parentThemePhp, + }); + + expect(childThemeName.text).toContain('Child Theme'); + expect(parentThemeName.text).toContain('Parent Theme'); + }); + + /** + * Test that startWPNow in "theme" mode auto activates the theme. + */ + test('startWPNow auto handles child theme with missing parent.', async () => { + const projectPath = path.join( + tmpExampleDirectory, + 'child-theme-missing-parent/child' + ); + const options = await getWpNowConfig({ path: projectPath }); + const { php } = await startWPNow(options); + + const childThemePhp = `get('Name'); + `; + + const childThemeName = await php.run({ + code: childThemePhp, + }); + + expect(childThemeName.text).toContain('Child Theme'); + }); + + /** + * Test that startWPNow in "theme" mode does not auto activate the theme the second time. + */ + test('startWPNow auto installs the theme', async () => { + const projectPath = path.join(tmpExampleDirectory, 'theme'); + const options = await getWpNowConfig({ path: projectPath }); + const { php } = await startWPNow(options); + const switchThemePhp = `get('Name'); + `; + const themeName = await phpSecondTime.run({ + code: codeActiveThemeNamePhp, + }); + + expect(themeName.text).toContain('Twenty Twenty-Three'); + }); + + /* + * Test that startWPNow doesn't leave dirty files. + * + * @see https://github.com/WordPress/playground-tools/issues/32 + */ + test.skip('startWPNow does not leave dirty folders and files', async () => { + const projectPath = path.join(tmpExampleDirectory, 'wordpress'); + const options = await getWpNowConfig({ path: projectPath }); + await startWPNow(options); + expect( + fs.existsSync( + path.join( + projectPath, + 'wp-content', + 'mu-plugins', + '0-allow-wp-org.php' + ) + ) + ).toBe(false); + expect( + fs.existsSync(path.join(projectPath, 'wp-content', 'database')) + ).toBe(false); + expect( + fs.existsSync( + path.join( + projectPath, + 'wp-content', + 'plugins', + 'sqlite-database-integration' + ) + ) + ).toBe(false); + expect( + fs.existsSync(path.join(projectPath, 'wp-content', 'db.php')) + ).toBe(false); + }); + + /** + * Test PHP integration test executing runCli. + */ + describe('validate php comand arguments passed through yargs', () => { + const phpExampleDir = path.join(__dirname, 'execute-php'); + const argv = process.argv; + let output = ''; + let processExitMock; + beforeEach(() => { + vi.spyOn(console, 'log').mockImplementation((newLine: string) => { + output += `${newLine}\n`; + }); + processExitMock = vi + .spyOn(process, 'exit') + .mockImplementation(() => null); + }); + + afterEach(() => { + output = ''; + process.argv = argv; + vi.resetAllMocks(); + }); + + test('php should receive the correct yargs arguments', async () => { + process.argv = ['node', 'wp-now', 'php', '--', '--version']; + await runCli(); + expect(output).toMatch(/PHP 8\.0(.*)\(cli\)/i); + expect(processExitMock).toHaveBeenCalledWith(0); + }); + + test('wp-now should change the php version', async () => { + process.argv = [ + 'node', + 'wp-now', + 'php', + '--php=7.4', + '--', + '--version', + ]; + await runCli(); + expect(output).toMatch(/PHP 7\.4(.*)\(cli\)/i); + expect(processExitMock).toHaveBeenCalledWith(0); + }); + + test('php should execute a file', async () => { + const filePath = path.join(phpExampleDir, 'php-version.php'); + process.argv = ['node', 'wp-now', 'php', filePath]; + await runCli(); + expect(output).toMatch(/8\.0/i); + expect(processExitMock).toHaveBeenCalledWith(0); + }); + + test('php should execute a file and change php version', async () => { + const filePath = path.join(phpExampleDir, 'php-version.php'); + process.argv = [ + 'node', + 'wp-now', + 'php', + '--php=7.4', + '--', + filePath, + ]; + await runCli(); + expect(output).toMatch(/7\.4/i); + expect(processExitMock).toHaveBeenCalledWith(0); + }); + }); + + /** + * Test startServer. + */ + describe('startServer', () => { + let stopServer, options; + + beforeEach(async () => { + const projectPath = path.join( + tmpExampleDirectory, + 'theme-with-assets' + ); + const config = await getWpNowConfig({ path: projectPath }); + const server = await startServer(config); + stopServer = server.stopServer; + options = server.options; + }); + + afterEach(async () => { + options = null; + await stopServer(); + }); + + /** + * Test that startServer compresses the text files correctly. + */ + test.each([ + ['html', ''], + ['css', '/wp-content/themes/theme-with-assets/style.css'], + [ + 'javascript', + '/wp-content/themes/theme-with-assets/javascript.js', + ], + ])('startServer compresses the %s file', async (_, file) => { + const req = await fetch(`${options.absoluteUrl}${file}`); + expect(req.headers.get('content-encoding')).toBe('gzip'); + }); + + /** + * Test that startServer doesn't compress non text files. + */ + test.each([ + ['png', '/wp-content/themes/theme-with-assets/image.png'], + ['jpg', '/wp-content/themes/theme-with-assets/image.jpg'], + ])("startServer doesn't compress the %s file", async (_, file) => { + const req = await fetch(`${options.absoluteUrl}${file}`); + expect(req.headers.get('content-encoding')).toBe(null); + }); + }); + /** * Test blueprints execution. */