diff --git a/packages/docs/site/docs/developers/05-local-development/04-wp-playground-cli.md b/packages/docs/site/docs/developers/05-local-development/04-wp-playground-cli.md index ff6e8513f7..1608484770 100644 --- a/packages/docs/site/docs/developers/05-local-development/04-wp-playground-cli.md +++ b/packages/docs/site/docs/developers/05-local-development/04-wp-playground-cli.md @@ -110,7 +110,9 @@ The `server` command supports the following optional arguments: - `--blueprint=`: The path to a JSON Blueprint file to execute. - `--blueprint-may-read-adjacent-files`: Consent flag: Allow "bundled" resources in a local blueprint to read files in the same directory as the blueprint file. - `--login`: Automatically log the user in as an administrator. -- `--skip-wordpress-setup`: Do not download or install WordPress. Useful if you are mounting a full WordPress directory. +- `--skip-wordpress-download`: Skip downloading and unzipping WordPress. Useful if you mount a `/wordpress` directory that already contains core files. +- `--skip-wordpress-install`: Skip running the WordPress installer. Useful when your mounted `/wordpress` directory is already installed. +- `--skip-wordpress-setup`: **Deprecated.** Use `--skip-wordpress-download` and/or `--skip-wordpress-install` instead. - `--skip-sqlite-setup`: Do not set up the SQLite database integration. - `--verbosity`: Output logs and progress messages. Choices are "quiet", "normal" or "debug". Defaults to "normal". - `--debug`: Print the PHP error log if an error occurs during boot. diff --git a/packages/playground/cli/README.md b/packages/playground/cli/README.md index fbc43cc08e..4498aec8fa 100644 --- a/packages/playground/cli/README.md +++ b/packages/playground/cli/README.md @@ -91,7 +91,9 @@ The `server` command supports the following optional arguments: - `--blueprint=`: The path to a JSON Blueprint file to execute. - `--blueprint-may-read-adjacent-files`: Consent flag: Allow "bundled" resources in a local blueprint to read files in the same directory as the blueprint file. - `--login`: Automatically log the user in as an administrator. -- `--skip-wordpress-setup`: Do not download or install WordPress. Useful if you are mounting a full WordPress directory. +- `--skip-wordpress-download`: Skip downloading and unzipping WordPress. Useful when the `/wordpress` directory already contains the core files. +- `--skip-wordpress-install`: Skip running the WordPress installer. Useful when the mounted `/wordpress` site is already installed. +- `--skip-wordpress-setup`: **Deprecated.** Use `--skip-wordpress-download` and/or `--skip-wordpress-install` instead. - `--skip-sqlite-setup`: Do not set up the SQLite database integration. - `--verbosity`: Output logs and progress messages (choices: "quiet", "normal", "debug"). Defaults to "normal". diff --git a/packages/playground/cli/src/blueprints-v1/blueprints-v1-handler.ts b/packages/playground/cli/src/blueprints-v1/blueprints-v1-handler.ts index 36a83a5eff..d9c6a67fe2 100644 --- a/packages/playground/cli/src/blueprints-v1/blueprints-v1-handler.ts +++ b/packages/playground/cli/src/blueprints-v1/blueprints-v1-handler.ts @@ -11,7 +11,10 @@ import { import { RecommendedPHPVersion, zipDirectory } from '@wp-playground/common'; import fs from 'fs'; import path from 'path'; -import { resolveWordPressRelease } from '@wp-playground/wordpress'; +import { + resolveWordPressRelease, + type InstallationMode, +} from '@wp-playground/wordpress'; import { CACHE_FOLDER, cachedDownload, @@ -62,11 +65,18 @@ export class BlueprintsV1Handler { fileLockManagerPort: NodeMessagePort, nativeInternalDirPath: string ) { + const skipWordPressDownload = + this.args.skipWordPressDownload === true || + this.args.skipWordPressSetup === true; + const skipWordPressInstall = + this.args.skipWordPressInstall === true || + this.args.skipWordPressSetup === true; + let wpDetails: any = undefined; // @TODO: Rename to FetchProgressMonitor. There's nothing Emscripten // about that class anymore. const monitor = new EmscriptenDownloadMonitor(); - if (!this.args.skipWordPressSetup) { + if (!skipWordPressDownload) { let progressReached100 = false; monitor.addEventListener('progress', (( e: CustomEvent @@ -135,8 +145,19 @@ export class BlueprintsV1Handler { this.getEffectiveBlueprint() ); await playground.useFileLockManager(fileLockManagerPort); + let installationMode: InstallationMode | undefined; + if (skipWordPressInstall) { + installationMode = false; + } else if (skipWordPressDownload) { + installationMode = 'wp-installer'; + } + const resolvedPhpVersion = + runtimeConfiguration.phpVersion ?? + this.args.php ?? + RecommendedPHPVersion; + this.phpVersion = resolvedPhpVersion; await playground.bootAsPrimaryWorker({ - phpVersion: runtimeConfiguration.phpVersion, + phpVersion: resolvedPhpVersion, wpVersion: runtimeConfiguration.wpVersion, siteUrl: this.siteUrl, mountsBeforeWpInstall, @@ -151,6 +172,7 @@ export class BlueprintsV1Handler { internalCookieStore: this.args.internalCookieStore, withXdebug: this.args.xdebug, nativeInternalDirPath, + installationMode, }); if ( diff --git a/packages/playground/cli/src/blueprints-v1/worker-thread-v1.ts b/packages/playground/cli/src/blueprints-v1/worker-thread-v1.ts index 72eea9d7be..b60c0b8daf 100644 --- a/packages/playground/cli/src/blueprints-v1/worker-thread-v1.ts +++ b/packages/playground/cli/src/blueprints-v1/worker-thread-v1.ts @@ -14,6 +14,7 @@ import { RecommendedPHPVersion } from '@wp-playground/common'; import { bootRequestHandler, bootWordPressAndRequestHandler, + type InstallationMode, } from '@wp-playground/wordpress'; import { rootCertificates } from 'tls'; import { jspi } from 'wasm-feature-detect'; @@ -52,6 +53,7 @@ export type PrimaryWorkerBootOptions = WorkerBootOptions & { wordPressZip?: ArrayBuffer; sqliteIntegrationPluginZip?: ArrayBuffer; dataSqlPath?: string; + installationMode?: InstallationMode; }; interface WorkerBootRequestHandlerOptions { @@ -138,6 +140,7 @@ export class PlaygroundCliBlueprintV1Worker extends PHPWorker { internalCookieStore, withXdebug, nativeInternalDirPath, + installationMode, }: PrimaryWorkerBootOptions) { if (this.booted) { throw new Error('Playground already booted'); @@ -202,6 +205,7 @@ export class PlaygroundCliBlueprintV1Worker extends PHPWorker { }, cookieStore: internalCookieStore ? undefined : false, dataSqlPath, + installationMode, spawnHandler: sandboxedSpawnHandlerFactory, async onPHPInstanceCreated(php) { await mountResources(php, mountsBeforeWpInstall); diff --git a/packages/playground/cli/src/run-cli.ts b/packages/playground/cli/src/run-cli.ts index 94857b9b3f..1ac7ebffd8 100644 --- a/packages/playground/cli/src/run-cli.ts +++ b/packages/playground/cli/src/run-cli.ts @@ -155,7 +155,20 @@ export async function parseOptionsAndRunCLI() { }) .option('skip-wordpress-setup', { describe: - 'Do not download, unzip, and install WordPress. Useful for mounting a pre-configured WordPress directory at /wordpress.', + '[DEPRECATED] Do not download, unzip, and install WordPress. Use --skip-wordpress-download and/or --skip-wordpress-install instead.', + type: 'boolean', + default: false, + hidden: true, + }) + .option('skip-wordpress-download', { + describe: + 'Skip downloading and unzipping WordPress. Use when the /wordpress directory is already populated.', + type: 'boolean', + default: false, + }) + .option('skip-wordpress-install', { + describe: + 'Skip running the WordPress installer. Use when the mounted /wordpress site is already installed.', type: 'boolean', default: false, }) @@ -250,10 +263,17 @@ export async function parseOptionsAndRunCLI() { .strictOptions() .check(async (args) => { // Support multiple spellings of "WordPress" - if ( - args['skip-wordpress-setup'] || - args['skipWordpressSetup'] - ) { + const skipWordPressSetup = + args['skip-wordpress-setup'] === true || + args['skipWordpressSetup'] === true; + const skipWordPressDownload = + args['skip-wordpress-download'] === true || + args['skipWordpressDownload'] === true; + const skipWordPressInstall = + args['skip-wordpress-install'] === true || + args['skipWordpressInstall'] === true; + + if (skipWordPressSetup) { args['skipWordPressSetup'] = true; } @@ -304,9 +324,13 @@ export async function parseOptionsAndRunCLI() { if (args['experimental-blueprints-v2-runner'] === true) { if (args['mode'] !== undefined) { - if ('skip-wordpress-setup' in args) { + if ( + skipWordPressSetup || + skipWordPressDownload || + skipWordPressInstall + ) { throw new Error( - 'The --skipWordPressSetup option cannot be used with the --mode option. Use one or the other.' + 'The WordPress lifecycle skip options cannot be used with the --mode option. Use one or the other.' ); } if ('skip-sqlite-setup' in args) { @@ -321,7 +345,11 @@ export async function parseOptionsAndRunCLI() { } } else { // Support the legacy v1 runner options - if (args['skip-wordpress-setup'] === true) { + if ( + skipWordPressSetup || + skipWordPressDownload || + skipWordPressInstall + ) { args['mode'] = 'apply-to-existing-site'; } else { args['mode'] = 'create-new-site'; @@ -354,6 +382,20 @@ export async function parseOptionsAndRunCLI() { yargsObject.wrap(yargsObject.terminalWidth()); const args = await yargsObject.argv; + const parsedSkipWordPressSetup = args['skipWordPressSetup'] === true; + const parsedSkipWordPressDownload = + args['skipWordPressDownload'] === true || + args['skip-wordpress-download'] === true; + const parsedSkipWordPressInstall = + args['skipWordPressInstall'] === true || + args['skip-wordpress-install'] === true; + + args['skipWordPressSetup'] = parsedSkipWordPressSetup; + args['skipWordPressDownload'] = + parsedSkipWordPressSetup || parsedSkipWordPressDownload; + args['skipWordPressInstall'] = + parsedSkipWordPressSetup || parsedSkipWordPressInstall; + const command = args._[0] as string; if (!['run-blueprint', 'server', 'build-snapshot'].includes(command)) { @@ -422,6 +464,8 @@ export interface RunCLIArgs { 'experimental-blueprints-v2-runner'?: boolean; // --------- Blueprint V1 args ----------- + skipWordPressDownload?: boolean; + skipWordPressInstall?: boolean; skipWordPressSetup?: boolean; skipSqliteSetup?: boolean; followSymlinks?: boolean; @@ -463,6 +507,23 @@ export async function runCLI(args: RunCLIArgs): Promise { worker: Worker; }[] = []; + if (args.skipWordPressSetup) { + logger.warn( + 'The skipWordPressSetup option is deprecated. Use skipWordPressDownload and skipWordPressInstall instead.' + ); + args = { + ...args, + skipWordPressDownload: true, + skipWordPressInstall: true, + }; + } + + args = { + ...args, + skipWordPressDownload: args.skipWordPressDownload === true, + skipWordPressInstall: args.skipWordPressInstall === true, + }; + /** * Expand auto-mounts to include the necessary mounts and steps * when running in auto-mount mode. diff --git a/packages/playground/cli/tests/run-cli.spec.ts b/packages/playground/cli/tests/run-cli.spec.ts index c9dcf13071..c3da7df5b6 100644 --- a/packages/playground/cli/tests/run-cli.spec.ts +++ b/packages/playground/cli/tests/run-cli.spec.ts @@ -59,7 +59,8 @@ describe.each(blueprintVersions)( php: '8.0', // Let's skip the cost of WordPress setup because it is // irrelevant for this test. - skipWordPressSetup: true, + skipWordPressDownload: true, + skipWordPressInstall: true, skipSqliteSetup: true, blueprint: undefined, }); @@ -643,7 +644,8 @@ describe('other run-cli behaviors', () => { test('should clear old auto-login cookie', async () => { cliServer = await runCLI({ command: 'server', - skipWordPressSetup: true, + skipWordPressDownload: true, + skipWordPressInstall: true, skipSqliteSetup: true, blueprint: undefined, }); @@ -677,7 +679,8 @@ describe('other run-cli behaviors', () => { test('should return 500 when the request handler throws an error', async () => { cliServer = await runCLI({ command: 'server', - skipWordPressSetup: true, + skipWordPressDownload: true, + skipWordPressInstall: true, blueprint: undefined, }); diff --git a/packages/playground/wordpress/src/boot.ts b/packages/playground/wordpress/src/boot.ts index db42c8a6d8..8f4d8140fb 100644 --- a/packages/playground/wordpress/src/boot.ts +++ b/packages/playground/wordpress/src/boot.ts @@ -38,6 +38,7 @@ export type PHPInstanceCreatedHook = ( ) => Promise; export type DatabaseType = 'sqlite' | 'mysql' | 'custom'; +export type InstallationMode = 'wp-installer' | 'sql-dump' | false; export async function bootWordPressAndRequestHandler( options: BootRequestHandlerOptions & BootWordPressOptions @@ -137,6 +138,8 @@ export interface BootWordPressOptions { * and WP_SITEURL constants in WordPress. */ siteUrl: string; + /** Override automatic installation behavior. */ + installationMode?: InstallationMode; } /** @@ -201,7 +204,21 @@ export async function bootWordPress( ); } - if (options.wordPressZip && !options.dataSqlPath) { + const installationMode = + options.installationMode ?? + (options.dataSqlPath + ? 'sql-dump' + : options.wordPressZip + ? 'wp-installer' + : false); + + if (installationMode === 'sql-dump' && !options.dataSqlPath) { + throw new Error( + 'The "sql-dump" installation mode requires a dataSqlPath to be provided.' + ); + } + + if (installationMode === 'wp-installer') { if (!(await isWordPressInstalled(php))) { // Install WordPress if it's not installed. await installWordPress(php); diff --git a/packages/playground/wordpress/src/index.ts b/packages/playground/wordpress/src/index.ts index df0fbcfb4d..82959af7a3 100644 --- a/packages/playground/wordpress/src/index.ts +++ b/packages/playground/wordpress/src/index.ts @@ -9,7 +9,11 @@ export { bootRequestHandler, getFileNotFoundActionForWordPress, } from './boot'; -export type { PhpIniOptions, PHPInstanceCreatedHook } from './boot'; +export type { + PhpIniOptions, + PHPInstanceCreatedHook, + InstallationMode, +} from './boot'; export { defineWpConfigConstants, ensureWpConfig } from './rewrite-wp-config'; export { getLoadedWordPressVersion } from './version-detect';