From 0318c771ac5fde7858bb7bdb852200385d1baea3 Mon Sep 17 00:00:00 2001 From: Adam Zielinski Date: Fri, 24 Nov 2023 14:35:37 +0100 Subject: [PATCH] Experimental: Adds declarative properties to the Blueprint schema (#788) ## What is this PR doing? First stab at https://github.com/WordPress/wordpress-playground/issues/787 This PR proposes a first few declarative Blueprint properties: ```js { "landingPage": "/wp-admin/post-new.php", "plugins": [ "gutenberg", "classic-editor", "https://mysite.com/plugin.zip", { "resource": "url", "https://mysite.com/plugin.zip" } ], "login": true, "siteOptions": { "blogname": "My site!" } } ``` Under the hood, all these declarative properties are translated into `steps` and prepended to the list of Blueprint steps. Therefore, the imperative `steps` API is still supported and can be mixed with the declarative syntax. These new options are all marked as deprecated from the start to discourage their use. Once they're stable, I'll remove the deprecation notice. ## Testing instructions * Pull this branch * Paste the below Blueprint at http://localhost:5400/website-server/# <-- here * Confirm it worked as expected ```js ```js { "landingPage": "/wp-admin/post-new.php", "plugins": [ "gutenberg", "classic-editor" ], "login": true, "siteOptions": { "blogname": "My site!" } } ``` cc @dmsnell @seanmorris --- .../blueprints/public/blueprint-schema.json | 395 ++++++++++-------- .../blueprints/src/lib/blueprint.ts | 40 +- .../playground/blueprints/src/lib/compile.ts | 51 ++- 3 files changed, 317 insertions(+), 169 deletions(-) diff --git a/packages/playground/blueprints/public/blueprint-schema.json b/packages/playground/blueprints/public/blueprint-schema.json index bfb10dfaaf..a0daf2e66b 100644 --- a/packages/playground/blueprints/public/blueprint-schema.json +++ b/packages/playground/blueprints/public/blueprint-schema.json @@ -46,6 +46,67 @@ }, "additionalProperties": false }, + "constants": { + "type": "object", + "additionalProperties": { + "type": "string" + }, + "description": "PHP Constants to define on every request", + "deprecated": "This experimental option will change without warning.\nUse `steps` instead." + }, + "plugins": { + "type": "array", + "items": { + "anyOf": [ + { + "type": "string" + }, + { + "$ref": "#/definitions/FileReference" + } + ] + }, + "description": "WordPress plugins to install and activate", + "deprecated": "This experimental option will change without warning.\nUse `steps` instead." + }, + "siteOptions": { + "type": "object", + "additionalProperties": { + "type": "string" + }, + "properties": { + "blogname": { + "type": "string", + "description": "The site title" + } + }, + "description": "WordPress site options to define", + "deprecated": "This experimental option will change without warning.\nUse `steps` instead." + }, + "login": { + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "object", + "properties": { + "username": { + "type": "string" + }, + "password": { + "type": "string" + } + }, + "required": [ + "username", + "password" + ], + "additionalProperties": false + } + ], + "description": "User to log in as. If true, logs the user in as admin/password." + }, "phpExtensionBundles": { "type": "array", "items": { @@ -75,7 +136,7 @@ } ] }, - "description": "The steps to run." + "description": "The steps to run after every other operation in this Blueprint was executed." }, "$schema": { "type": "string" @@ -96,6 +157,172 @@ "7.0" ] }, + "FileReference": { + "anyOf": [ + { + "$ref": "#/definitions/VFSReference" + }, + { + "$ref": "#/definitions/LiteralReference" + }, + { + "$ref": "#/definitions/CoreThemeReference" + }, + { + "$ref": "#/definitions/CorePluginReference" + }, + { + "$ref": "#/definitions/UrlReference" + } + ] + }, + "VFSReference": { + "type": "object", + "properties": { + "resource": { + "type": "string", + "const": "vfs", + "description": "Identifies the file resource as Virtual File System (VFS)" + }, + "path": { + "type": "string", + "description": "The path to the file in the VFS" + } + }, + "required": [ + "resource", + "path" + ], + "additionalProperties": false + }, + "LiteralReference": { + "type": "object", + "properties": { + "resource": { + "type": "string", + "const": "literal", + "description": "Identifies the file resource as a literal file" + }, + "name": { + "type": "string", + "description": "The name of the file" + }, + "contents": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "object", + "properties": { + "BYTES_PER_ELEMENT": { + "type": "number" + }, + "buffer": { + "type": "object", + "properties": { + "byteLength": { + "type": "number" + } + }, + "required": [ + "byteLength" + ], + "additionalProperties": false + }, + "byteLength": { + "type": "number" + }, + "byteOffset": { + "type": "number" + }, + "length": { + "type": "number" + } + }, + "required": [ + "BYTES_PER_ELEMENT", + "buffer", + "byteLength", + "byteOffset", + "length" + ], + "additionalProperties": { + "type": "number" + } + } + ], + "description": "The contents of the file" + } + }, + "required": [ + "resource", + "name", + "contents" + ], + "additionalProperties": false + }, + "CoreThemeReference": { + "type": "object", + "properties": { + "resource": { + "type": "string", + "const": "wordpress.org/themes", + "description": "Identifies the file resource as a WordPress Core theme" + }, + "slug": { + "type": "string", + "description": "The slug of the WordPress Core theme" + } + }, + "required": [ + "resource", + "slug" + ], + "additionalProperties": false + }, + "CorePluginReference": { + "type": "object", + "properties": { + "resource": { + "type": "string", + "const": "wordpress.org/plugins", + "description": "Identifies the file resource as a WordPress Core plugin" + }, + "slug": { + "type": "string", + "description": "The slug of the WordPress Core plugin" + } + }, + "required": [ + "resource", + "slug" + ], + "additionalProperties": false + }, + "UrlReference": { + "type": "object", + "properties": { + "resource": { + "type": "string", + "const": "url", + "description": "Identifies the file resource as a URL" + }, + "url": { + "type": "string", + "description": "The URL of the file" + }, + "caption": { + "type": "string", + "description": "Optional caption for displaying a progress message" + } + }, + "required": [ + "resource", + "url" + ], + "additionalProperties": false + }, "SupportedPHPExtensionBundle": { "type": "string", "const": "kitchen-sink" @@ -961,172 +1188,6 @@ } ] }, - "FileReference": { - "anyOf": [ - { - "$ref": "#/definitions/VFSReference" - }, - { - "$ref": "#/definitions/LiteralReference" - }, - { - "$ref": "#/definitions/CoreThemeReference" - }, - { - "$ref": "#/definitions/CorePluginReference" - }, - { - "$ref": "#/definitions/UrlReference" - } - ] - }, - "VFSReference": { - "type": "object", - "properties": { - "resource": { - "type": "string", - "const": "vfs", - "description": "Identifies the file resource as Virtual File System (VFS)" - }, - "path": { - "type": "string", - "description": "The path to the file in the VFS" - } - }, - "required": [ - "resource", - "path" - ], - "additionalProperties": false - }, - "LiteralReference": { - "type": "object", - "properties": { - "resource": { - "type": "string", - "const": "literal", - "description": "Identifies the file resource as a literal file" - }, - "name": { - "type": "string", - "description": "The name of the file" - }, - "contents": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "object", - "properties": { - "BYTES_PER_ELEMENT": { - "type": "number" - }, - "buffer": { - "type": "object", - "properties": { - "byteLength": { - "type": "number" - } - }, - "required": [ - "byteLength" - ], - "additionalProperties": false - }, - "byteLength": { - "type": "number" - }, - "byteOffset": { - "type": "number" - }, - "length": { - "type": "number" - } - }, - "required": [ - "BYTES_PER_ELEMENT", - "buffer", - "byteLength", - "byteOffset", - "length" - ], - "additionalProperties": { - "type": "number" - } - } - ], - "description": "The contents of the file" - } - }, - "required": [ - "resource", - "name", - "contents" - ], - "additionalProperties": false - }, - "CoreThemeReference": { - "type": "object", - "properties": { - "resource": { - "type": "string", - "const": "wordpress.org/themes", - "description": "Identifies the file resource as a WordPress Core theme" - }, - "slug": { - "type": "string", - "description": "The slug of the WordPress Core theme" - } - }, - "required": [ - "resource", - "slug" - ], - "additionalProperties": false - }, - "CorePluginReference": { - "type": "object", - "properties": { - "resource": { - "type": "string", - "const": "wordpress.org/plugins", - "description": "Identifies the file resource as a WordPress Core plugin" - }, - "slug": { - "type": "string", - "description": "The slug of the WordPress Core plugin" - } - }, - "required": [ - "resource", - "slug" - ], - "additionalProperties": false - }, - "UrlReference": { - "type": "object", - "properties": { - "resource": { - "type": "string", - "const": "url", - "description": "Identifies the file resource as a URL" - }, - "url": { - "type": "string", - "description": "The URL of the file" - }, - "caption": { - "type": "string", - "description": "Optional caption for displaying a progress message" - } - }, - "required": [ - "resource", - "url" - ], - "additionalProperties": false - }, "InstallPluginOptions": { "type": "object", "properties": { diff --git a/packages/playground/blueprints/src/lib/blueprint.ts b/packages/playground/blueprints/src/lib/blueprint.ts index 2cb2e2eea8..64c4229cda 100644 --- a/packages/playground/blueprints/src/lib/blueprint.ts +++ b/packages/playground/blueprints/src/lib/blueprint.ts @@ -3,6 +3,7 @@ import { SupportedPHPVersion, } from '@php-wasm/universal'; import { StepDefinition } from './steps'; +import { FileReference } from './resources'; export interface Blueprint { /** @@ -28,12 +29,49 @@ export interface Blueprint { /** Should boot with support for network request via wp_safe_remote_get? */ networking?: boolean; }; + + /** + * PHP Constants to define on every request + * @deprecated This experimental option will change without warning. + * Use `steps` instead. + */ + constants?: Record; + + /** + * WordPress plugins to install and activate + * @deprecated This experimental option will change without warning. + * Use `steps` instead. + */ + plugins?: Array; + + /** + * WordPress site options to define + * @deprecated This experimental option will change without warning. + * Use `steps` instead. + */ + siteOptions?: Record & { + /** The site title */ + blogname?: string; + }; + + /** + * User to log in as. + * If true, logs the user in as admin/password. + */ + login?: + | boolean + | { + username: string; + password: string; + }; + /** * The PHP extensions to use. */ phpExtensionBundles?: SupportedPHPExtensionBundle[]; /** - * The steps to run. + * The steps to run after every other operation in this Blueprint was + * executed. */ steps?: Array; } diff --git a/packages/playground/blueprints/src/lib/compile.ts b/packages/playground/blueprints/src/lib/compile.ts index 253d4ce45f..21e0dc5e56 100644 --- a/packages/playground/blueprints/src/lib/compile.ts +++ b/packages/playground/blueprints/src/lib/compile.ts @@ -10,7 +10,7 @@ import { UniversalPHP, } from '@php-wasm/universal'; import type { SupportedPHPExtensionBundle } from '@php-wasm/universal'; -import { isFileReference, Resource } from './resources'; +import { FileReference, isFileReference, Resource } from './resources'; import { Step, StepDefinition } from './steps'; import * as stepHandlers from './steps/handlers'; import { Blueprint } from './blueprint'; @@ -79,6 +79,55 @@ export function compileBlueprint( ...blueprint, steps: (blueprint.steps || []).filter(isStepDefinition), }; + // Experimental declarative syntax {{{ + if (blueprint.constants) { + blueprint.steps!.unshift({ + step: 'defineWpConfigConsts', + consts: blueprint.constants, + }); + } + if (blueprint.siteOptions) { + blueprint.steps!.unshift({ + step: 'setSiteOptions', + options: blueprint.siteOptions, + }); + } + if (blueprint.plugins) { + // Translate an array of strings into a map of pluginName => true to + // install the latest version of the plugin from wordpress.org + const steps = blueprint.plugins + .map((value) => { + if (typeof value === 'string') { + if (value.startsWith('https://')) { + return { + resource: 'url', + url: value, + } as FileReference; + } else { + return { + resource: 'wordpress.org/plugins', + slug: value, + } as FileReference; + } + } + return value; + }) + .map((resource) => ({ + step: 'installPlugin', + pluginZipFile: resource, + })) as StepDefinition[]; + blueprint.steps!.unshift(...steps); + } + if (blueprint.login) { + blueprint.steps!.push({ + step: 'login', + ...(blueprint.login === true + ? { username: 'admin', password: 'password' } + : blueprint.login), + }); + } + // }}} + const { valid, errors } = validateBlueprint(blueprint); if (!valid) { const e = new Error(