Skip to content

Latest commit

 

History

History

environments

README.md

WordPress.org Local Development Environments

Local development environments for WordPress.org projects, powered by wp-env.

Prerequisites

  • A Docker runtime, installed and running (see below)
  • Node.js — the version in .nvmrc (nvm use)

Docker runtime

On macOS, use Colima. It's free and open source and needs no account.

brew install colima docker docker-buildx docker-compose
colima start --cpu 4 --memory 4

Homebrew installs Buildx and Compose as Docker CLI plugins outside the CLI's search path. On Apple Silicon, point the CLI at them by adding this property to ~/.docker/config.json, keeping whatever else is already in the file:

"cliPluginsExtraDirs": [ "/opt/homebrew/lib/docker/cli-plugins" ]

Colima remembers the CPU and memory settings, so later starts are just colima start. To start it at login instead, run brew services start colima.

On Windows, consider using Rancher Desktop with its dockerd (moby) engine, or Docker Desktop. On Linux, use Docker Engine.

Setup

From the environments/ directory:

npm install

Available Environments

Plugin Directory

A local instance of the WordPress.org Plugin Directory with the plugin directory plugin, theme, and supporting mu-plugins.

Start:

npm run plugins:env start

Import a plugin by slug:

npm run plugins:import -- akismet

Re-seed plugins (clears import flag, then re-imports):

npm run plugins:refresh

Access: http://localhost:8888

WP CLI:

npx wp-env run cli wp <command>

Run tests:

npm run plugins:test

Theme Directory

A local instance of the WordPress.org Theme Directory with the theme directory plugin, Theme Check, the wporg-themes-2024 frontend theme, and supporting mu-plugins. Themes are imported from the live WordPress.org themes API.

Start:

npm run themes:env start

Re-import themes (on demand, without clearing existing data):

npm run themes:import

Re-seed themes (clears import flag, then re-imports):

npm run themes:refresh

Access: http://localhost:8888

WP CLI:

npm run themes:env -- run cli -- wp <command>

Run tests:

npm run themes:test

Jobs

A local instance of jobs.wordpress.net with the JobsWP plugin, theme, sample job categories, and sample job posts.

Start:

npm run jobs:env start

Access: http://localhost:8888

WP CLI:

npm run jobs:env -- run cli -- wp <command>

Run tests (the JobsWP plugin's PHPUnit suite, in a dedicated test environment):

npm run jobs:test

Browse Happy

A local instance of browsehappy.com with the theme.

Start:

npm run browsehappy:env start

Access: http://localhost:8888

Translate

A local instance of translate.wordpress.org with GlotPress, the wporg-gp-* plugins active on production, and the pub/wporg theme.

Start:

npm run translate:env start

First start auto-imports hello-dolly (plugin) and twentytwenty (theme) so the WordPress Plugins and WordPress Themes project containers have real fixtures. It also seeds a few demo Translation Events (active, upcoming, past, and draft) with hosts and attendees.

Access: http://localhost:8888

Users: admin / password is a GlotPress global administrator, so it can approve translations everywhere and never sees a permission check fail. translator / password is a plain subscriber with no GlotPress permissions — use it to check what a contributor sees, such as suggestions going to waiting instead of current. The dev login button fills in admin, so type the contributor credentials by hand.

Import a plugin or theme's translations on demand:

npm run translate:import -- plugin akismet
npm run translate:import -- theme twentytwentyfour

Seed demo events on demand (idempotent):

npm run translate:seed-events

Re-seed (clears the seed flag so the next start re-imports fixtures):

npm run translate:refresh

WP CLI:

npm run translate:env -- run cli -- wp <command>

Run tests (the Translation Events plugin's PHPUnit suite, in a dedicated test environment):

npm run translate:test

Local overrides: create translate/.wp-env.override.json (git-ignored) to override config values like WP_HOME / WP_SITEURL for testing behind a custom hostname.

Translation Events 2024 design: the events routes render the legacy templates unless the new block theme is enabled. To preview it, add "config": { "TRANSLATION_EVENTS_NEW_DESIGN": true } to translate/.wp-env.override.json and restart.

Support Forums

A local instance of the WordPress.org Support Forums with bbPress, the wporg-support-2024 theme, and the wporg-bbp-* supporting plugins. It runs as a multisite network, because the forums read the plugin and theme directories out of sibling sub-sites.

Start:

npm run support:env start

Access: http://localhost:8888

Re-seed (clears the seed flag, then re-runs the seed):

npm run support:refresh

WP CLI:

npm run support:env -- run cli -- wp <command>

Add --url=localhost:8888/rosetta (or /plugins, /themes) to target a sub-site.

The network:

Blog Path Purpose
1 / The global support forums — the site under development
2 /plugins Plugin Directory, a dependency of the /plugin/<slug>/ forum views
3 /themes Theme Directory, a dependency of the /theme/<slug>/ forum views
4 /rosetta A locale ("Rosetta") forum, for the locale-only code paths

The blog IDs are pinned in .wp-env.json (WPORG_PLUGIN_DIRECTORY_BLOGID and friends), so the seed creates the sub-sites in that order and fails loudly if they come out differently.

Note that the plugin and theme directories here exist only as forum dependencies. Use the Plugin Directory and Theme Directory environments to work on the directories themselves.

On production each locale forum is its own network with IS_ROSETTA_NETWORK defined, which a single wp-config.php cannot express. mocks/mu-plugins/wporg-support-env.php defines it for the blog named by WPORG_LOCAL_ROSETTA_BLOGID instead.

Forum IDs: the Plugins, Themes and Reviews forums are created as the post IDs that Plugin::PLUGINS_FORUM_ID and Support_Compat::HIDDEN_FORUMS hard-code for production (21261, 21262, 21272, plus two legacy IDs). The directory compat views, the review forum, and the hidden-forum filtering all key off those, so they cannot be left to auto-increment.

What to try: hello-dolly is seeded on the plugin directory with a committer, a contributor and a support rep; twentytwentyfour is seeded on the theme directory with an author. Visit /plugin/hello-dolly/, /theme/twentytwentyfour/, and either followed by reviews/. Ratings submitted through the review form persist in the local ratings table, so the star filters and rating edits work.

Local boundaries. The environment deliberately does not reach production:

  • Badge assignments would otherwise be a live POST to profiles.wordpress.org. Badge_Automation registers its hooks locally, because assign_badge() lives in the mounted mu-plugins/pub/profile-helpers.php, and Profiles\queue() dispatches synchronously for anything that is not production while api() only redirects the URL for staging. mocks/mu-plugins/wporg-profiles-local.php answers those requests and records the associations in local tables.
  • Outbound mail is short-circuited; the forums mail on subscriptions, moderation and reports.
  • WPORG_Ratings is a local stand-in. Ratings_Compat guards on class_exists() alone, so the stub implements every method it calls rather than a subset.
  • SSO, two-factor account management and the rest of the Profiles service are not reproduced.

User accounts:

All accounts use the password password.

Username Role on / Role on /rosetta Notes
admin Network administrator Network administrator Full network admin access
keymaster bbp_keymaster bbp_participant Top-level forum admin; can manage all forum content
moderator bbp_moderator bbp_participant Can moderate topics and replies
rosettakeymaster bbp_participant bbp_keymaster Keymaster of the locale forum
rosettamoderator bbp_participant bbp_moderator Moderator of the locale forum
pluginauthor bbp_participant bbp_participant Committer on the seeded Hello Dolly plugin
plugincontributor bbp_participant bbp_participant Contributor on the seeded Hello Dolly plugin
pluginsupport bbp_participant bbp_participant Support rep on the seeded Hello Dolly plugin
themeauthor bbp_participant bbp_participant Author of the seeded Twenty Twenty-Four theme
themesupport bbp_participant bbp_participant Theme support rep; no production equivalent yet
visitor bbp_participant bbp_participant Regular forum visitor

Block editor: the forums use the block editor through Blocks Everywhere, which is not installed here — it needs a Gutenberg old enough to break against WordPress trunk until Automattic/blocks-everywhere#211 lands. Plugin::__construct() loads the block support only when that plugin is present, so the environment runs on bbPress' plain editor until then.

WordPress.org SSO

A test-only environment for the shared single sign-on code in common/includes/wporg-sso/. The SSO is a library rather than a plugin, so it is mounted at wp-content/wporg-sso instead of being activated, and its PHPUnit suite runs from there.

WP_ENVIRONMENT_TYPE is set to production so the SSO uses the hosts it uses in production (login.wordpress.org and friends) rather than the shortcuts it takes on local installs.

Run tests:

npm run sso:test

Handbook (in-plugin)

The Handbook plugin has its own .wp-env.json in wordpress.org/public_html/wp-content/plugins/handbook/.

Start:

cd wordpress.org/public_html/wp-content/plugins/handbook
npx wp-env start

Run tests: use the test environment in this directory instead — it starts a dedicated instance and runs the suite in one step:

npm run handbook:test

FreeScout (helpdesk)

A local FreeScout with the modules from freescout.wordpress.net/. FreeScout isn't WordPress, so this environment uses Docker Compose directly instead of wp-env.

Start:

npm run freescout:start

Then open http://127.0.0.1:8890 and log in with WordPress.org: a mock login page lets you pick an account, e.g. admin, which is connected to the FreeScout admin. Its other accounts cover new users (reviewer), no two-factor authentication (no2fa), and blocked accounts (blocked). Replies sent to senders land in Mailpit at http://127.0.0.1:8891.

What's running:

Service Purpose
app FreeScout's latest release on nginx and PHP-FPM 8.4, with the scheduler and queue worker production would run from cron.
db MariaDB 11.4, with a separate freescout-test database for the tests.
greenmail Mail server FreeScout fetches the Plugins and Themes mailboxes from, over IMAP. Two sample emails are delivered on first start.
mailpit Catches outgoing mail.
mock-api Stands in for api.wordpress.org/dotorg/freescout/: checks request signatures, answers sidebar requests with panels recorded from the real endpoints for a real account (obenland's), and logs webhook events (npm run freescout:logs). Also stands in for login.wordpress.org's SAML identity provider at http://127.0.0.1:8892/idp, signing with a key that's only for local development.

freescout.wordpress.net/Modules/ is mounted as FreeScout's Modules/ folder, like production's checkout. Every start switches all modules on and runs freescout:after-app-update, as production does after a deploy. After switching branches, run npm run freescout:setup: FreeScout errors on every page while it still has a removed module cached.

Setup also adds sample data, set up like the HelpScout helpdesk but with made-up people and conversations. It adds the Learn WordPress mailbox, without incoming email. It also adds four made-up agents and 39 made-up conversations in Plugins, Themes and Learn WordPress from the last eight weeks, with replies, notes, and closed, pending, and spam conversations. Five of the Plugins ones are plugin reviews, one for each state of the Plugin Review panel: issues waiting for an update, a rename, an ownership check, an update uploaded, and an approval. It only adds what's missing, and only to a FreeScout on 127.0.0.1 or localhost.

Premium modules: to try them, copy them in from a clone of the private freescout-paid-modules repository. They must never be committed here, so exclude each one first. From environments/:

paid=~/path/to/freescout-paid-modules
exclude="$(git rev-parse --path-format=absolute --git-common-dir)/info/exclude"
for module in "$paid"/Modules/*/; do
	target="freescout.wordpress.net/Modules/$(basename "$module")"
	grep -qxF "$target/" "$exclude" || echo "$target/" >> "$exclude"
	rsync -a --delete "$module" "../$target/"
done
npm run freescout:setup

Setup switches them on, and adds sample data for each one that's there, with HelpScout's names:

  • Tags: some of HelpScout's tags and colors, on conversations.
  • Custom Fields: the Learn WordPress fields, with values. Plugins and Themes have none.
  • Saved Replies: some of HelpScout's saved replies in Plugins and Themes, with short made-up texts.
  • Teams: the Plugin, Themes and Learn teams, with conversations assigned to some. The admin is in Plugin Reviews, so they get the Plugin Review panel. Names longer than FreeScout's 20 characters are shortened.
  • Custom Folders: "New WordPress" in Plugins, for replies to new WordPress version emails.
  • Mentions: a note in which one agent mentions another.
  • Workflows: some of HelpScout's workflows, with made-up rules. Automatic ones only run for new email with matching subjects, or a tag an agent adds; none run for the sample conversations.
  • Reports: response and resolution times, ready right away.
  • API & Webhooks: a read-only API key for the admin, and a webhook for new conversations to the mock API, which turns it away.

Slack stays unconfigured: it needs a Slack app.

To send more mail in:

npm run freescout:env -- exec app bash -c 'printf "Subject: Hi\r\n\r\nHello\r\n" | curl -s --url smtp://greenmail:3025 --mail-from someone@example.org --mail-rcpt plugins@wordpress.test --upload-file -'

Real WordPress.org data: with access to the helpdesk's API secret (FREESCOUT_SECRET on api.wordpress.org), the sidebar can show real WordPress.org data instead of the mock's. Store it in the macOS Keychain once: paste the secret when asked (twice). Pasting it there keeps it out of your shell history; add -U to replace a stored secret.

security add-generic-password -a "$USER" -s wporg-freescout-api -w

Then start FreeScout against api.wordpress.org:

WPORG_API_URL=https://api.wordpress.org/dotorg/freescout/ WPORG_API_SECRET="$(security find-generic-password -s wporg-freescout-api -w)" WPORG_USERNAME=<your username> npm run freescout:start

This is production data, so WPOrgWebhooks stays off: local conversations would count toward production's contributor stats. The mock's accounts don't exist there, so add WPORG_USERNAME=<your username> to connect the FreeScout admin to your own account, and type that username at the mock login. Logging in syncs the admin's name and email from WordPress.org. Run npm run freescout:start without the variables to go back to the mock; the tests always use their own values.

If your hosts file points api.wordpress.org at a sandbox, FreeScout reaches it without your proxy and gets a 403; comment the entry out while testing.

Run tests:

npm run freescout:test

Other FreeScout versions: FREESCOUT_REF takes any FreeScout branch or release tag, e.g. FREESCOUT_REF=master npm run freescout:start. Each gets its own database and storage; pass the same FREESCOUT_REF to stop or destroy it. Artisan: npm run freescout:artisan -- <command>. Stop / destroy: npm run freescout:stop, npm run freescout:destroy.

Common Commands

# Stop an environment (replace plugins with jobs, etc.)
npm run plugins:env stop
npm run jobs:env stop

# Destroy an environment (removes all data)
npm run plugins:env destroy
npm run jobs:env destroy

# View logs
npm run plugins:env logs
npm run jobs:env logs

All commands should be run from the environments/ directory unless otherwise noted.