From 9ebeb51d02aa19e165c0e011c0f7077e03233021 Mon Sep 17 00:00:00 2001 From: Brandon Payton Date: Thu, 3 Oct 2024 23:45:14 -0400 Subject: [PATCH] Support installing WP as needed in Playground remote --- packages/playground/client/src/index.ts | 5 +- packages/playground/remote/src/lib/index.ts | 1 + .../remote/src/lib/worker-thread.ts | 21 +++++++- .../playground/remote/src/lib/worker-utils.ts | 44 ++++++++++++++++ .../src/lib/state/redux/boot-site-client.ts | 51 ++----------------- 5 files changed, 74 insertions(+), 48 deletions(-) diff --git a/packages/playground/client/src/index.ts b/packages/playground/client/src/index.ts index d559877f16..56c2884df6 100644 --- a/packages/playground/client/src/index.ts +++ b/packages/playground/client/src/index.ts @@ -67,8 +67,11 @@ export interface StartPlaygroundOptions { */ onBeforeBlueprint?: () => Promise; mounts?: Array; - shouldInstallWordPress?: boolean; /** + * Whether to install WordPress. Value may be boolean or 'auto'. + * If 'auto', WordPress will be installed if it is not already installed. + */ + shouldInstallWordPress?: boolean | 'auto'; /** * The string prefix used in the site URL served by the currently * running remote.html. E.g. for a prefix like `/scope:playground/`, * the scope would be `playground`. See the `@php-wasm/scopes` package diff --git a/packages/playground/remote/src/lib/index.ts b/packages/playground/remote/src/lib/index.ts index 518d8848a8..2f71323d63 100644 --- a/packages/playground/remote/src/lib/index.ts +++ b/packages/playground/remote/src/lib/index.ts @@ -1,5 +1,6 @@ export * from './boot-playground-remote'; export * from './playground-client'; +export { looksLikePlaygroundDirectory } from './worker-utils'; export { MinifiedWordPressVersions, MinifiedWordPressVersionsList, diff --git a/packages/playground/remote/src/lib/worker-thread.ts b/packages/playground/remote/src/lib/worker-thread.ts index d5100dd27a..574c588569 100644 --- a/packages/playground/remote/src/lib/worker-thread.ts +++ b/packages/playground/remote/src/lib/worker-thread.ts @@ -21,6 +21,7 @@ import { spawnHandlerFactory, backfillStaticFilesRemovedFromMinifiedBuild, hasCachedStaticFilesRemovedFromMinifiedBuild, + looksLikePlaygroundDirectory, } from './worker-utils'; import { EmscriptenDownloadMonitor } from '@php-wasm/progress'; import { createMemoizedFetch } from './create-memoized-fetch'; @@ -72,7 +73,7 @@ export type WorkerBootOptions = { scope: string; withNetworking: boolean; mounts?: Array; - shouldInstallWordPress?: boolean; + shouldInstallWordPress?: boolean | 'auto'; }; /** @inheritDoc PHPClient */ @@ -188,6 +189,24 @@ export class PlaygroundWorkerEndpoint extends PHPWorker { } try { + if (shouldInstallWordPress === 'auto') { + // Default to installing WordPress unless we detect + // it in one of the mounts. + shouldInstallWordPress = true; + + // NOTE: This check is insufficient if a complete WordPress + // installation is composed of multiple mounts. + for (const mount of mounts) { + const dirHandle = await directoryHandleFromMountDevice( + mount.device + ); + if (await looksLikePlaygroundDirectory(dirHandle)) { + shouldInstallWordPress = false; + break; + } + } + } + // Start downloading WordPress if needed let wordPressRequest = null; if (shouldInstallWordPress) { diff --git a/packages/playground/remote/src/lib/worker-utils.ts b/packages/playground/remote/src/lib/worker-utils.ts index 75dd62965e..9790f41912 100644 --- a/packages/playground/remote/src/lib/worker-utils.ts +++ b/packages/playground/remote/src/lib/worker-utils.ts @@ -245,3 +245,47 @@ export async function getWordPressStaticZipUrl(php: PHP) { } return joinPaths('/', staticAssetsDirectory, 'wordpress-static.zip'); } + +/** + * Check if the given directory handle directory is a Playground directory. + * + * @TODO: Create a shared package like @wp-playground/wordpress for such utilities + * and bring in the context detection logic from wp-now – only express it in terms of + * either abstract FS operations or isomorphic PHP FS operations. + * (we can't just use Node.js require('fs') in the browser, for example) + * + * @TODO: Reuse the "isWordPressInstalled" logic implemented in the boot protocol. + * Perhaps mount OPFS first, and only then check for the presence of the + * WordPress installation? Or, if not, perhaps implement a shared file access + * abstraction that can be used both with the PHP module and OPFS directory handles? + * + * @param dirHandle + */ +export async function looksLikePlaygroundDirectory( + dirHandle: FileSystemDirectoryHandle +) { + // Run this loop just to trigger an exception if the directory handle is no good. + // eslint-disable-next-line @typescript-eslint/no-unused-vars + for await (const _ of dirHandle.keys()) { + break; + } + + try { + /** + * Assume it's a Playground directory if these files exist: + * - wp-config.php + * - wp-content/database/.ht.sqlite + */ + await dirHandle.getFileHandle('wp-config.php', { create: false }); + const wpContent = await dirHandle.getDirectoryHandle('wp-content', { + create: false, + }); + const database = await wpContent.getDirectoryHandle('database', { + create: false, + }); + await database.getFileHandle('.ht.sqlite', { create: false }); + } catch (e) { + return false; + } + return true; +} diff --git a/packages/playground/website/src/lib/state/redux/boot-site-client.ts b/packages/playground/website/src/lib/state/redux/boot-site-client.ts index 7df006b8bf..f0fe3d6490 100644 --- a/packages/playground/website/src/lib/state/redux/boot-site-client.ts +++ b/packages/playground/website/src/lib/state/redux/boot-site-client.ts @@ -15,7 +15,10 @@ import { Blueprint, StepDefinition } from '@wp-playground/blueprints'; import { logger } from '@php-wasm/logger'; import { setupPostMessageRelay } from '@php-wasm/web'; import { startPlaygroundWeb } from '@wp-playground/client'; -import { PlaygroundClient } from '@wp-playground/remote'; +import { + PlaygroundClient, + looksLikePlaygroundDirectory, +} from '@wp-playground/remote'; import { getRemoteUrl } from '../../config'; import { setActiveModal, setActiveSiteError } from './slice-ui'; import { PlaygroundDispatch, PlaygroundReduxState } from './store'; @@ -72,7 +75,7 @@ export function bootSiteClient( let isWordPressInstalled = false; if (mountDescriptor) { try { - isWordPressInstalled = await playgroundAvailableInOpfs( + isWordPressInstalled = await looksLikePlaygroundDirectory( await directoryHandleFromMountDevice(mountDescriptor.device) ); } catch (e) { @@ -207,47 +210,3 @@ export function bootSiteClient( signal.onabort = null; }; } - -/** - * Check if the given directory handle directory is a Playground directory. - * - * @TODO: Create a shared package like @wp-playground/wordpress for such utilities - * and bring in the context detection logic from wp-now – only express it in terms of - * either abstract FS operations or isomorphic PHP FS operations. - * (we can't just use Node.js require('fs') in the browser, for example) - * - * @TODO: Reuse the "isWordPressInstalled" logic implemented in the boot protocol. - * Perhaps mount OPFS first, and only then check for the presence of the - * WordPress installation? Or, if not, perhaps implement a shared file access - * abstraction that can be used both with the PHP module and OPFS directory handles? - * - * @param dirHandle - */ -export async function playgroundAvailableInOpfs( - dirHandle: FileSystemDirectoryHandle -) { - // Run this loop just to trigger an exception if the directory handle is no good. - // eslint-disable-next-line @typescript-eslint/no-unused-vars - for await (const _ of dirHandle.keys()) { - break; - } - - try { - /** - * Assume it's a Playground directory if these files exist: - * - wp-config.php - * - wp-content/database/.ht.sqlite - */ - await dirHandle.getFileHandle('wp-config.php', { create: false }); - const wpContent = await dirHandle.getDirectoryHandle('wp-content', { - create: false, - }); - const database = await wpContent.getDirectoryHandle('database', { - create: false, - }); - await database.getFileHandle('.ht.sqlite', { create: false }); - } catch (e) { - return false; - } - return true; -}