Skip to content

[Blueprints] Expand v2 with WP-less installs, baseline content policy, site resources, and multisite - #4026

Merged
adamziel merged 11 commits into
trunkfrom
blueprint-v2-schema-conformance
Jul 16, 2026
Merged

[Blueprints] Expand v2 with WP-less installs, baseline content policy, site resources, and multisite#4026
adamziel merged 11 commits into
trunkfrom
blueprint-v2-schema-conformance

Conversation

@adamziel

@adamziel adamziel commented Jul 12, 2026

Copy link
Copy Markdown
Collaborator

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

contentBaseline selects 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 contentBaseline is 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 resetData step, 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 resetData when content must be removed at a specific point in additionalStepsAfterExecution rather than during site creation:

{
    "version": 2,
    "additionalStepsAfterExecution": [
        {
            "step": "resetData",
            "contentTypes": ["pages", "comments"]
        }
    ]
}
/**
 * Content types to remove. Omit this property to remove all posts, pages,
 * custom post types, and comments.
 */
contentTypes?: Array<'posts' | 'pages' | 'comments'>;

Fresh-site users baseline

usersBaseline selects 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": {}
        }
    ]
}
/**
 * `keep-all` or omission retains the administrator created by WordPress.
 * `empty` removes the installation users and requires
 * `contentBaseline: "empty"` plus at least one declared administrator.
 *
 * @default "keep-all"
 */
usersBaseline?: 'keep-all' | 'empty';

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"]
        }
    }
}
/**
 * Additional PHP extensions to load before execution. The only accepted
 * item today is `intl`. Omission or an empty list requests no additional
 * extension and disables nothing.
 */
loadPhpExtensions?: Array<'intl'>;

Target-site file references

{
    "version": 2,
    "plugins": ["woocommerce"],
    "content": [
        {
            "type": "wxr",
            "source": "site:wp-content/plugins/woocommerce/sample-data/sample_products.xml"
        }
    ]
}

FileDataReference now accepts site: paths. ./file.xml and /file.xml still 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 resetData step as contentBaseline. A baseline clears vanilla WordPress before any Blueprint setup, but these Blueprints clear content after some steps already ran:

Moving any of those resets to contentBaseline changes the execution order. Their v2 conversions therefore keep an explicit resetData step at the original position.

Testing

npm exec nx -- run playground-blueprints:build:blueprint-schema
npm exec nx -- run playground-blueprints:typecheck
npm exec nx -- run playground-blueprints:lint
npm exec nx -- run playground-blueprints:test:vite --testFile=reset-data.spec.ts
npm exec nx -- run playground-blueprints:test:vite --testFile=compile.spec.ts
npm exec nx -- run playground-blueprints:test:vite --testFile=validate-blueprint-v2.spec.ts
npm exec nx -- run playground-blueprints:test:vite --testFile=runtime-smoke.spec.ts
npm exec nx -- run playground-blueprints:test:vite --testFile=schema-conformance-runtime.spec.ts
git diff --check

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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, and usersBaseline to 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 beta to 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 phpOnlyMode is true, the handler now ignores an explicit args.wordpressInstallMode that 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 for args.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.

Comment thread packages/playground/client/src/blueprints-v2-handler.ts
Comment thread packages/playground/client/src/blueprints-v2-handler.ts Outdated
Comment thread packages/playground/client/src/blueprints-v2-handler.ts
Comment thread packages/playground/blueprints/src/lib/steps/activate-plugin.ts
Comment thread packages/playground/blueprints/src/lib/v2/compile.ts Outdated
Comment thread packages/playground/blueprints/src/lib/v2/compile.ts
Comment thread packages/playground/cli/tests/blueprints-v2-handler.spec.ts Outdated
@adamziel adamziel changed the title [Blueprints] Add PHP targets and creation baselines to v2 Jul 12, 2026
@adamziel adamziel changed the title [Blueprints] Add a PHP-only target and fresh-site baselines to v2 Jul 12, 2026
@adamziel adamziel changed the title [Blueprints] Add PHP targets, fresh-site baselines, target-site sources, and multisite to v2 Jul 15, 2026
@adamziel adamziel changed the title [Blueprints] Add PHP-only versions, fresh-site baselines, target-site sources, and multisite to v2 Jul 15, 2026
@adamziel adamziel changed the title [Blueprints] Add wordpressVersion none, fresh-site baselines, target-site sources, and multisite to v2 Jul 15, 2026
@adamziel
adamziel merged commit 1dbe3c4 into trunk Jul 16, 2026
53 checks passed
@adamziel
adamziel deleted the blueprint-v2-schema-conformance branch July 16, 2026 00:42
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
```
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment