feat: Add local WordPress environment powered by wp-env#328
feat: Add local WordPress environment powered by wp-env#328
Conversation
Add @wordpress/env and the supporting configuration for a local WordPress development environment. This gives developers a zero-config way to run the full editor experience locally, including theme styles, media uploads, and plugin block assets. - .wp-env.json: Configures WordPress with the Gutenberg plugin and CORS mu-plugin - wp-env/mu-plugins/gutenbergkit-cors.php: Adds CORS headers for Vite dev/preview servers and native WebViews - bin/wp-env-setup.sh: Auto-provisions application password credentials after wp-env start - .gitignore: Excludes the generated credentials file - package.json: Adds @wordpress/env as a dev dependency
Add wp-env-start, wp-env-stop, wp-env-clean, wp-env-logs, and wp-env-cli targets for managing the local WordPress environment.
Add a "Local WordPress" entry that reads wp-env credentials and connects to the local environment. Shows a helpful error message with setup instructions when wp-env is not running. - ConfigurationItem: Add .localWordPress case and credentials loader - EditorList: Add "Local Development" section - SitePreparationView: Handle .localWordPress in startLoading() - Xcode scheme: Add WP_ENV_CREDENTIALS_PATH environment variable
Add a "Local WordPress" entry that reads wp-env credentials and connects to the local environment. Automatically remaps localhost to 10.0.2.2 for Android emulator access. - ConfigurationItem: Add LocalWordPress sealed class object - LocalWordPressCredentials: Load and parse credentials file with localhost-to-10.0.2.2 remapping - MainActivity: Add Local WordPress to configurations list and UI - SitePreparationActivity: Handle LocalWordPress serialization - SitePreparationViewModel: Handle LocalWordPress in startLoading() - build.gradle.kts: Add WP_ENV_CREDENTIALS_PATH build config field - strings.xml: Add Local WordPress string resources
- AGENTS.md: Add Local WordPress Environment section with make targets - docs/code/local-wordpress.md: Comprehensive guide covering setup, platform notes, and troubleshooting
Add a "wp-env" npm script and update the Makefile targets and setup script to use `npm run wp-env` instead of `npx wp-env`, aligning with how the Gutenberg repo invokes wp-env.
- Use ?rest_route=/ for the health check since pretty permalinks may not be active when wp-env first starts - Flush rewrite rules after readiness check so /wp-json/ works for the demo apps - Use npm run --silent to prevent npm banner output from leaking into the captured application password
…ation Make cannot pass --flags to recipe scripts. Switch to RESET=1 environment variable, consistent with the existing REFRESH_DEPS=1 and REFRESH_L10N=1 patterns. Usage: make wp-env-start RESET=1
- EditorList (iOS): Show dynamic footer text — "Not configured" with make command hint when credentials file is missing - SitePreparationView (iOS): Use ContentUnavailableView for errors, catch URLError for connection failures with a specific message - AppError: Make errorDescription optional to satisfy LocalizedError protocol correctly, fixing the generic error display - ConfigurationCard (Android): Show dynamic subtitle based on credentials availability - SitePreparationViewModel (Android): Catch ConnectException with a specific message when wp-env server is unreachable
- EditorList: Use neutral footer text that doesn't imply the server is running just because a credentials file exists - SitePreparationView: Catch all URLError types (not just a subset) to handle cannotFindHost and other connection failures
Add explicit import for java.util.Properties and move the local properties loading to a top-level val before the android block.
The LocalWordPress item was added to the configurations list but the LazyColumn only rendered BundledEditor and ConfiguredEditor instances explicitly. Add a dedicated item block for LocalWordPress.
The Android emulator cannot access host filesystem paths at runtime. Instead of reading a file path from local.properties, read the credentials JSON at build time in Gradle and inject the values as BuildConfig fields (WP_ENV_SITE_URL, WP_ENV_SITE_API_ROOT, WP_ENV_AUTH_HEADER). Developers need to rebuild the app after running make wp-env-start for credential changes to take effect.
org.json is not available on the Gradle classpath. Use Groovy's built-in JsonSlurper which is always available in Gradle builds.
WpLoginClient.apiDiscovery() forces HTTPS, which causes an SSL error when connecting to the HTTP-only local WordPress server. Add a direct HTTP fallback that fetches the REST API root and inspects the routes object, matching the approach used by the iOS implementation.
The Android emulator remaps localhost to 10.0.2.2, so the WebView's origin is http://10.0.2.2:5173 instead of http://localhost:5173. Add the 10.0.2.2 equivalents to the allowed CORS origins.
WordPress generates image URLs using its configured site URL (localhost:8888), which doesn't resolve inside the Android emulator. Document the WP-CLI commands to toggle the site URL between localhost and 10.0.2.2 for Android testing. Also fix outdated references to --reset flag and local.properties.
The `gutenberg.latest-stable.zip` URL caused wp-env to extract the plugin into a `gutenberg.latest-stable/` directory, because wp-env only strips numeric version suffixes from zip filenames. This broke Jetpack's editor-assets endpoint, which checks for `/plugins/gutenberg/` in asset URLs to identify Gutenberg assets. The mismatch caused Gutenberg-provided scripts like `react` and `react-jsx-runtime` to be unregistered, silently dropping `jetpack-blocks-editor` due to missing dependencies. Using `gutenberg.zip` still fetches the latest stable release but produces the correct `gutenberg/` directory name.
Install Jetpack alongside Gutenberg in the local WordPress environment and enable the blocks module during setup. This provides the `/wpcom/v2/editor-assets` endpoint and additional editor blocks for testing. Also gitignore plugin-generated files in `wp-env/mu-plugins/` while preserving our own CORS mu-plugin.
Rename "Default Editor" to "Standalone Editor" with a clearer description and merge the "Local Development" and "Editor Configurations" sections into a single "WordPress Sites" group, since both represent editors connected to a WordPress site.
Rename "Default editor" to "Standalone editor" with a clearer description, merge the Local WordPress and Editor Configurations sections into a single "WordPress Sites" group, and use the Article icon for the standalone editor.
…ld with wp-env When using the local wp-env WordPress site (HTTP) with the Android production build, the WebView blocked all HTTP resources as mixed content because bundled assets were served over HTTPS via WebViewAssetLoader. Use `setHttpAllowed(true)` on the asset loader when the configured site URL uses HTTP, so the editor page scheme matches the site and eliminates mixed content. Also add the `appassets.androidplatform.net` origin to the wp-env CORS mu-plugin so REST API and font requests are allowed from the production build origin.
…dler The OPTIONS preflight handler used a plain `else` fallback, which sent `Access-Control-Allow-Origin: *` for any unrecognized origin. Align it with the REST API handler by using `elseif ( empty( $origin ) )` so only requests with no Origin header receive the wildcard.
Shell `&&` binds tighter than `||`, so the condition evaluated as `RESET=true OR (RESET=1 AND file-exists)` instead of the intended `(RESET=true OR RESET=1) AND file-exists`. Group the OR clause explicitly with braces.
Document that setHttpAllowed(true) means unencrypted asset traffic, which is acceptable for local development but not production sites.
Replace `://localhost` instead of bare `localhost` so the substitution only targets the host portion of the URL, not occurrences in paths or query parameters.
The allowed-origins array and header-sending logic were duplicated between the rest_api_init and init handlers. Extract them into gutenbergkit_cors_allowed_origins() and gutenbergkit_cors_send_origin_headers() to keep a single source of truth and prevent the two lists from drifting apart.
|
This is looking really nice. |
Previously, setHttpAllowed(true) was enabled for any site URL using the http:// scheme. This could silently downgrade asset traffic to unencrypted for a non-local site. Restrict the allowance to known local hosts (localhost, 127.0.0.1, 10.0.2.2) so production sites always use HTTPS for the asset loader.
The discovery result was logged in the original code but was dropped when the else branch was rewritten to add the HTTP fallback. Restore it so the reason for WpLoginClient failure is visible in logcat.
| /** | ||
| * Discovers capabilities by directly fetching the REST API root and inspecting | ||
| * the `routes` object. This works over plain HTTP, unlike WpLoginClient which | ||
| * may require HTTPS. | ||
| */ | ||
| private fun discoverCapabilitiesViaHttp(siteApiRoot: String): SiteCapabilities { |
There was a problem hiding this comment.
Added to circumvent errors related to non-TLS connections when connecting to the local wp-env server. Maybe wordpress-rs should allow this? Maybe not?
uniffi.wp_api.InternalException: UnexpectedUniFFICallbackError(reason: "javax.net.ssl.SSLException: Unable to parse TLS packet header")
| if(this == empty) { | ||
| return false | ||
| } | ||
|
|
There was a problem hiding this comment.
Cherry picked from #326 to prevent empty bundle load error.
kean
left a comment
There was a problem hiding this comment.
Hey,I got it to work, and the changes look good.
Just to confirm, wordpress/env is a developmemnt-only dependency, correct?
I got a couple of more notes:
- It required Docker. It's not mentioned in the PR description, but is documented in the new file in docs. All I needed to do was to run it. It then downloaded and ran the image and set up the environment fully automatically.
- (dev experience) If you don't run a dev server, the editor shows an infinite spinner.
- (suggestion) It might be worth revisiting the AGENTS.md file, especially in the light of the recent discoveries about how it affects agents performance. I'd suggest moving the basic instructions needed for developers back to README.md and maybe also factoring out some of the coding guidelines and architecture docs.
| make test-android | ||
| ``` | ||
|
|
||
| ### Local WordPress Environment (wp-env) |
There was a problem hiding this comment.
Are agents expected to run these commands for setting up docker and wp-env? If not, it probably doesn't belong to AGENTS.md.
There was a problem hiding this comment.
Long term? Yes, to enable "E2E verification" that allows agents to verify their own work.
Today? Since we do not have an MCP for navigating the product in a simulator/emulator, E2E verification is not really possible. So, there is not really a context where an agent would use these today; it's more relevant to simply use the E2E test commands, which themselves set up the wp-env environment.
I do not believe we should remove this at this time. WDYT?
| @RESET=$(RESET) bash bin/wp-env-setup.sh | ||
|
|
||
| .PHONY: wp-env-stop | ||
| wp-env-stop: ## Stop the local WordPress environment |
There was a problem hiding this comment.
It might be worth calling wp-env directly. You could then use the existing commands and rely on the existing documentation for it: https://developer.wordpress.org/block-editor/reference-guides/packages/packages-env/.
There was a problem hiding this comment.
Do you mean run npm run wp-env rather than having bespoke make targets for interfacing with wp-env? If so, yes, the inability to easily pass npm script arguments is a downside of the make target approach.
That said, my approach so far with the Makefile is creating a simple interface for the project's common commands. One can always run and access the lower-level commands—npm, swift, kotlin—as necessary or desired.
If you find this approach confusing, I'm happy to revisit this, but I suggest we do so holistically in a separate pull request—potentially removing any/all make targets deemed superfluous or confusing.
WDYT?
There was a problem hiding this comment.
@kean thank you for reviewing and testing.
Just to confirm,
wordpress/envis a developmemnt-only dependency, correct?
Yes, it is listed beneath devDependencies in the package.json.
It required Docker. It's not mentioned in the PR description, but is documented in the new file in docs. All I needed to do was to run it. It then downloaded and ran the image and set up the environment fully automatically.
Apologies for not noting it in the PR description. I do not interpret this as actionable, but let me know if I am mistaken. Update: I added a note the PR description.
(dev experience) If you don't run a dev server, the editor shows an infinite spinner.
This is somewhat separate from the wp-env environment. In the future, we could explore adding page load error handlers to the WebView to better surface the inability to load the targeted web page (i.e., the editor HTML).
(suggestion) It might be worth revisiting the AGENTS.md file, especially in the light of the recent discoveries about how it affects agents performance. I'd suggest moving the basic instructions needed for developers back to README.md and maybe also factoring out some of the coding guidelines and architecture docs.
I'd love to hear more about your ideas. Given this feels a bit off topic from the focus of this PR, maybe we continue in chat.
| make test-android | ||
| ``` | ||
|
|
||
| ### Local WordPress Environment (wp-env) |
There was a problem hiding this comment.
Long term? Yes, to enable "E2E verification" that allows agents to verify their own work.
Today? Since we do not have an MCP for navigating the product in a simulator/emulator, E2E verification is not really possible. So, there is not really a context where an agent would use these today; it's more relevant to simply use the E2E test commands, which themselves set up the wp-env environment.
I do not believe we should remove this at this time. WDYT?
| @RESET=$(RESET) bash bin/wp-env-setup.sh | ||
|
|
||
| .PHONY: wp-env-stop | ||
| wp-env-stop: ## Stop the local WordPress environment |
There was a problem hiding this comment.
Do you mean run npm run wp-env rather than having bespoke make targets for interfacing with wp-env? If so, yes, the inability to easily pass npm script arguments is a downside of the make target approach.
That said, my approach so far with the Makefile is creating a simple interface for the project's common commands. One can always run and access the lower-level commands—npm, swift, kotlin—as necessary or desired.
If you find this approach confusing, I'm happy to revisit this, but I suggest we do so holistically in a separate pull request—potentially removing any/all make targets deemed superfluous or confusing.
WDYT?
What?
Adds a local WordPress development environment powered by
@wordpress/envfor testing the full editor experience with theme styles, media uploads, and plugin block assets.Why?
Close CMM-1265.
Testing the editor against a real WordPress instance is essential for validating theme styles, media workflows, and plugin block rendering. Previously, developers had to set up WordPress manually. This provides a one-command local environment that works with both iOS and Android demo apps.
How?
.wp-env.jsonconfig with Twenty Twenty-Five theme, Jetpack blocks plugin, and a CORS mu-plugin for cross-origin API accessbin/wp-env-setup.sh): Provisions application passwords and outputs credentials for the demo appswp-env-start,wp-env-stop,wp-env-clean,wp-env-logs,wp-env-cliWebViewAssetLoaderusessetHttpAllowed(true)when the site is HTTP, so the editor page scheme matches and avoids mixed content errorsappassets.androidplatform.netorigins (both HTTP and HTTPS) for Android production buildsTesting Instructions
Tip
Using
wp-envrequires a running Docker. Tools like Docker Desktop (paid license) and Colima (free) provide tools for to managing this.Accessibility Testing Instructions
N/A — no user-facing UI changes beyond adding a new list item in the demo apps.
Screenshots or screencast