[Blueprints] Expand v2 with WP-less installs, baseline content policy, site resources, and multisite - #4026
Merged
Merged
Conversation
Contributor
There was a problem hiding this comment.
Pull request overview
Note
Copilot was unable to run its full agentic suite in this review.
This PR extends Blueprint v2 to better represent and reproduce Gallery sites by introducing explicit application targets (WordPress vs PHP-only) and “creation baselines” for initial WordPress content/users, plus related runner/CLI behavior updates.
Changes:
- Add
target,contentBaseline, andusersBaselineto Blueprint v2 (schema, validation, compilation, and runtime behavior). - Support PHP-only (“no WordPress / no SQLite”) execution paths in both browser client and CLI runners.
- Improve operational robustness: resolve
betato Playground’s captured prerelease build and avoid scratch-log collisions during concurrent plugin activations.
Reviewed changes
Copilot reviewed 18 out of 20 changed files in this pull request and generated 7 comments.
Show a summary per file
| File | Description |
|---|---|
| packages/playground/client/src/blueprints-v2-handler.ts | Adjusts boot logic to force “no WP install” for target: "php". |
| packages/playground/client/src/blueprints-v2-handler.spec.ts | Adds coverage ensuring PHP targets boot without WordPress installation. |
| packages/playground/cli/src/resolve-cli-wordpress-version.ts | Introduces beta → captured prerelease resolution for CLI. |
| packages/playground/cli/src/blueprints-v2/blueprints-v2-handler.ts | Applies CLI beta resolution; adds PHP-only target install-mode handling. |
| packages/playground/cli/src/blueprints-v1/blueprints-v1-handler.ts | Aligns v1 CLI behavior with captured beta and effective runtime config. |
| packages/playground/cli/tests/blueprints-v2-handler.spec.ts | Adds CLI tests for PHP targets and captured prerelease beta; adds v1 handler test. |
| packages/playground/blueprints/src/lib/v2/compile.ts | Adds creation-baseline plan items and lowering to WP cleanup PHP steps. |
| packages/playground/blueprints/src/lib/v2/resolve-runtime-configuration.ts | Enables requested PHP extensions (currently intl) via application options. |
| packages/playground/blueprints/src/lib/steps/activate-plugin.ts | Gives each activation a unique scratch log and improves cleanup behavior. |
| packages/playground/blueprints/public/blueprint-schema.json | Extends the public JSON schema with target/baselines/extensions and constraints. |
| packages/playground/blueprints/src/lib/v2/wep-1-blueprint-v2-schema/appendix-A-blueprint-v2-schema.ts | Updates spec docs for target/baselines/extensions and WP version semantics. |
| packages/playground/blueprints/src/tests/* | Adds validation/runtime/config/compiler tests covering the new capabilities. |
Comments suppressed due to low confidence (1)
packages/playground/cli/src/blueprints-v2/blueprints-v2-handler.ts:351
- When
phpOnlyModeis true, the handler now ignores an explicitargs.wordpressInstallModethat requests WordPress (e.g.download-and-install) instead of throwing. Previously this conflict was rejected, which prevents surprising behavior where a user-provided CLI flag is silently overridden by the Blueprint. Consider restoring a conflict check forargs.wordpressInstallMode !== 'do-not-attempt-installing'(similar to the prior logic), or at least emitting a clear error/warning so the user can resolve the ambiguity intentionally.
function resolveV2WordPressInstallMode(
args: RunCLIArgs,
phpOnlyMode = false
): WordPressInstallMode {
if (phpOnlyMode) {
if (args.mode && args.mode !== 'mount-only') {
throw new Error(
'Conflicting options: WordPress was requested, but the Blueprint targets PHP without WordPress. Pick one.'
);
}
return 'do-not-attempt-installing';
}
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
adamziel
added a commit
that referenced
this pull request
Jul 16, 2026
Moves Blueprint v2 JSON Schema constraints into the TypeScript
declarations that define them.
Before this PR, a constrained string had two definitions:
```ts
export type ExecutionContextPath = `/${string}` | `./${string}`;
patchStringDefinition(
definitions,
'DataSources.ExecutionContextPath',
'^(?!.*(?:^|/)\\.\\.(?:/|$))(?:\\./|/).*$'
);
```
The TypeScript type was used by callers. Separately, `patchSchema()`
mutated the Blueprint v2 JSON Schema generated from
`BlueprintV2Declaration`. That schema is published in
`blueprint-schema.json` and compiled into the runtime validator. Adding
or renaming a type meant finding its generated definition name and
adding another patch. #4026 exposed the same problem when it added a
`TargetSitePath` constraint.
This PR makes the schema rule part of the declaration:
```ts
/**
* @astype string
* @pattern ^(?!.*(?:^|/)\.\.(?:/|$))(?:\./|/).*$
*/
export type ExecutionContextPath = `/${string}` | `./${string}`;
```
`ts-json-schema-generator-v2` reads these annotations. A small
normalization step removes the redundant union that the generator
retains alongside `@asType string`. `patchSchema()` and its
definition-name-specific mutations are gone.
The remaining generator stages do three separate jobs: preserve
document-wide invariants TypeScript cannot express, add AJV
discriminators, and replace the published portable URL pattern with
exact WHATWG URL validation in the runtime-only schema.
The generated schema accepts the same declarations as current `trunk`.
Its checked-in diff only renames an opaque position-based alias emitted
by the generator. The merge with #4026 moved its `TargetSitePath` and
`wordpressVersion: "none"` constraints into the declarations while
retaining its path-lowering and document-wide invariant code.
Test with:
```sh
npm exec -- nx run playground-blueprints:build:blueprint-schema
npm exec -- nx run playground-blueprints:test:vite --testFiles=src/tests/v2/validate-blueprint-v2.spec.ts --testFiles=src/tests/v2/schema-conformance.spec.ts --testFiles=src/tests/schema/public-schema.spec.ts
npm exec -- nx run playground-blueprints:typecheck
npm exec -- nx run playground-blueprints:lint
```
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Blueprint v2 now has schema and runner coverage for the Gallery and Automattic library cases that were blocked by missing runtime choices, target-site files, and multisite setup.
Before this PR, v2 could not say "run PHP without WordPress", "start this fresh WordPress site without starter content", "read the WXR file that an earlier plugin install just created under
wp-content", or "turn this install into a multisite network before the next WP-CLI command". Those are real Blueprint shapes. WooCommerce sample data lives below the installed plugin directory. Newspack needs network setup before it creates a second site.This PR adds the missing v2 declarations.
PHP without WordPress
{ "version": 2, "wordpressVersion": "none" }wordpressVersion: 'none'boots PHP only. It does not download WordPress or initialize SQLite. The browser client and CLI both honor it.Fresh-site content baseline
contentBaselineselects which content from a vanilla WordPress installation survives before the rest of the Blueprint runs.Keep the starter posts, pages, and comments. This is also the default when
contentBaselineis omitted:{ "version": 2, "contentBaseline": "keep-all" }Remove all starter posts, pages, and comments:
{ "version": 2, "contentBaseline": "empty" }Keep one content type:
{ "version": 2, "contentBaseline": "posts" }Keep several content types:
{ "version": 2, "contentBaseline": ["posts", "pages"] }A list must be non-empty and contain no duplicates. Comments can only be retained together with both posts and pages, so retaining comments requires
["posts", "pages", "comments"].The baseline is skipped when applying a Blueprint to an existing site, so it cannot erase mounted content. It lowers to the existing
resetDatastep, which now accepts the content types to remove. The handler uses WordPress APIs, removes linked data, resets empty table IDs, and clears the stale Calendar cache. Runtime values are passed as environment variables instead of being interpolated into PHP source.Ordered content reset
Use
resetDatawhen content must be removed at a specific point inadditionalStepsAfterExecutionrather than during site creation:{ "version": 2, "additionalStepsAfterExecution": [ { "step": "resetData", "contentTypes": ["pages", "comments"] } ] }Fresh-site users baseline
usersBaselineselects which users from a vanilla WordPress installation survive before declared users are created:{ "version": 2, "contentBaseline": "empty", "usersBaseline": "empty", "users": [ { "username": "new-admin", "email": "new-admin@example.com", "role": "administrator", "meta": {} } ] }The baseline is skipped when applying a Blueprint to an existing site, so it cannot remove users from that site.
Intl extension
A Blueprint can ask Playground to load Intl before execution:
{ "version": 2, "applicationOptions": { "wordpress-playground": { "loadPhpExtensions": ["intl"] } } }Target-site file references
{ "version": 2, "plugins": ["woocommerce"], "content": [ { "type": "wxr", "source": "site:wp-content/plugins/woocommerce/sample-data/sample_products.xml" } ] }FileDataReferencenow acceptssite:paths../file.xmland/file.xmlstill refer to the immutable Blueprint execution context.site:...refers to the mutable target WordPress root. It is resolved when the consuming step runs, so a previous plugin install can create the file. The resolver keeps the path inside the target root and never exposes a host filesystem path.Multisite initialization step
V2 gains an explicit multisite step. This example creates and installs the multisite config on the fly:
{ "$schema": "https://playground.wordpress.net/blueprint-schema.json", "version": 2, "additionalStepsAfterExecution": [ { "step": "enableMultisite" }, { "step": "wp-cli", "command": "wp site create --slug=food" } ] }It uses the same multisite step handler as Blueprints v1.
Conversion results
The public Gallery audit converts all 57 Blueprints. All 57 v1/v2 pairs now produce the same normalized database and file state.
Resets after setup
Four Gallery Blueprints cannot express their
resetDatastep ascontentBaseline. A baseline clears vanilla WordPress before any Blueprint setup, but these Blueprints clear content after some steps already ran:brewcommerceinstalls a theme and WooCommerce and extracts uploads before resetting content.reset-data-and-import-contentreads the default posts, resets them, then reads the empty state.install-activate-setup-theme-from-gh-repoandstylish-press,resetDatais the first explicit step, but top-level plugins are installed before the steps array runs.Moving any of those resets to
contentBaselinechanges the execution order. Their v2 conversions therefore keep an explicitresetDatastep at the original position.Testing