Skip to content

Commit

Permalink
wp-now: execute blueprint steps given a file path (WordPress#82)
Browse files Browse the repository at this point in the history
- Add Blueprint support for wp-now
  • Loading branch information
sejas committed Jun 23, 2023
1 parent fc891ad commit 02f25d4
Show file tree
Hide file tree
Showing 9 changed files with 236 additions and 6 deletions.
2 changes: 1 addition & 1 deletion .nvmrc
Original file line number Diff line number Diff line change
@@ -1 +1 @@
v18.13.0
v20.0.0
87 changes: 87 additions & 0 deletions packages/wp-now/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ wp-now php my-file.php
- `--php=<version>`: 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=<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=<version>`: 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=<path>`: 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=<path>` and `--php=<version>` arguments.

Expand All @@ -65,6 +66,92 @@ Of these, `wp-now php` currently supports the `--path=<path>` and `--php=<versio
- The `~/.wp-now` home directory is used to store the WP versions and the `wp-content` folders for projects using 'theme', 'plugin', 'wp-content', and 'playground' modes. The path to `wp-content` directory for the 'plugin', 'theme', and 'wp-content' modes is `~/.wp-now/wp-content/${projectName}-${directoryHash}`. 'playground' mode shares the same `~/.wp-now/wp-content/playground` directory, regardless of where it's started.
- For the database setup, `wp-now` is using [SQLite database integration plugin](https://wordpress.org/plugins/sqlite-database-integration/). The path to SQLite database is ` ~/.wp-now/wp-content/${projectName}-${directoryHash}/database/.ht.sqlite`

## Using Blueprints

Blueprints are JSON files with a list of steps to execute after starting wp-now. They can be used to automate the setup of a WordPress site, including defining wp-config constants, installing plugins, themes, and content.

## Defining a custom URL

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`. Note that the `virtualize` is set to `true` to avoid modifying the `wp-config.php` file that is shared between all the projects.

```json
{
"steps": [
{
"step": "defineWpConfigConsts",
"consts": {
"WP_HOME": "http://myurl.wpnow:8881",
"WP_SITEURL": "http://myurl.wpnow:8881"
},
"virtualize": true
}
]
}
```

This step can be also used along with `ngrok`, in this case you can run `ngrok http 8881`, copy the ngrok URL and replace `WP_HOME` and `WP_SITEURL` in the blueprint file.

If you prefer to use a different port, you can use the `--port` argument when starting the server.
`wp-now start --blueprint=path/to/blueprint-example.json --port=80`

The Blueprint to listen on port `80` will look like this:

```json
{
"steps": [
{
"step": "defineWpConfigConsts",
"consts": {
"WP_HOME": "http://myurl.wpnow",
"WP_SITEURL": "http://myurl.wpnow"
},
"virtualize": true
}
]
}
```

## 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)
Expand Down
57 changes: 53 additions & 4 deletions packages/wp-now/src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,21 +3,22 @@ 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 {
php?: string;
path?: string;
wp?: string;
port?: number;
blueprint?: string;
}

export const enum WPNowMode {
Expand All @@ -41,6 +42,7 @@ export interface WPNowOptions {
wpContentPath?: string;
wordPressVersion?: string;
numberOfPhpInstances?: number;
blueprintObject?: Blueprint;
}

export const DEFAULT_OPTIONS: WPNowOptions = {
Expand All @@ -63,12 +65,23 @@ export interface WPEnvOptions {
mappings: Object;
}

let absoluteUrlFromBlueprint = '';

async function getAbsoluteURL() {
const port = await portFinder.getOpenPort();
if (isGitHubCodespace) {
return getCodeSpaceURL(port);
}
return `http://localhost:${port}`;

if (absoluteUrlFromBlueprint) {
return absoluteUrlFromBlueprint;
}

const url = 'http://localhost';
if (port === 80) {
return url;
}
return `${url}:${port}`;
}

function getWpContentHomePath(projectPath: string, mode: string) {
Expand Down Expand Up @@ -135,5 +148,41 @@ 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')
);

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;
}
2 changes: 1 addition & 1 deletion packages/wp-now/src/main.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { runCli } from './run-cli';

const requiredMajorVersion = 18;
const requiredMajorVersion = 20;

const currentNodeVersion = parseInt(process.versions?.node?.split('.')?.[0]);

Expand Down
5 changes: 5 additions & 0 deletions packages/wp-now/src/run-cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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...');
Expand All @@ -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);
Expand Down
12 changes: 12 additions & 0 deletions packages/wp-now/src/tests/blueprints/wp-config.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"steps": [
{
"step": "defineWpConfigConsts",
"consts": {
"WP_HOME": "http://127.0.0.1",
"WP_SITEURL": "http://127.0.0.1"
},
"virtualize": true
}
]
}
11 changes: 11 additions & 0 deletions packages/wp-now/src/tests/blueprints/wp-debug.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"steps": [
{
"step": "defineWpConfigConsts",
"consts": {
"WP_DEBUG_LOG": "/var/www/html/wp-content/themes/fake/example.log"
},
"virtualize": true
}
]
}
53 changes: 53 additions & 0 deletions packages/wp-now/src/tests/wp-now.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -737,6 +737,59 @@ describe('Test starting different modes', () => {
expect(req.headers.get('content-encoding')).toBe(null);
});
});

/**
* Test blueprints execution.
*/
describe('blueprints', () => {
const blueprintExamplesPath = path.join(__dirname, 'blueprints');

afterEach(() => {
// Clean the custom url from the SQLite database
fs.rmSync(
path.join(getWpNowTmpPath(), 'wp-content', 'playground'),
{ recursive: true }
);
});

test('setting wp-config variable WP_DEBUG_LOG through blueprint', async () => {
const options = await getWpNowConfig({
blueprint: path.join(blueprintExamplesPath, 'wp-debug.json'),
});
const { php, stopServer } = await startServer(options);
php.writeFile(
`${php.documentRoot}/print-constants.php`,
`<?php echo WP_DEBUG_LOG;`
);
const result = await php.request({
method: 'GET',
url: '/print-constants.php',
});
expect(result.text).toMatch(
'/var/www/html/wp-content/themes/fake/example.log'
);
await stopServer();
});

test('setting wp-config variable WP_SITEURL through blueprint', async () => {
const options = await getWpNowConfig({
blueprint: path.join(blueprintExamplesPath, 'wp-config.json'),
});
const { php, stopServer } = await startServer(options);
expect(options.absoluteUrl).toBe('http://127.0.0.1');

php.writeFile(
`${php.documentRoot}/print-constants.php`,
`<?php echo WP_SITEURL;`
);
const result = await php.request({
method: 'GET',
url: '/print-constants.php',
});
expect(result.text).toMatch('http://127.0.0.1');
await stopServer();
});
});
});

/**
Expand Down
13 changes: 13 additions & 0 deletions packages/wp-now/src/wp-now.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -119,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',
Expand Down

0 comments on commit 02f25d4

Please sign in to comment.