wp-env: Add opt-in --auto-port flag for automatic port selection#74472
wp-env: Add opt-in --auto-port flag for automatic port selection#74472youknowriad merged 21 commits intoWordPress:trunkfrom
Conversation
|
The following accounts have interacted with this PR and/or linked issues. I will continue to update these lists as activity occurs. You can also manually ask me to refresh this list by adding the If you're merging code through a pull request on GitHub, copy and paste the following into the bottom of the merge commit message. To understand the WordPress project's expectations around crediting contributors, please review the Contributor Attribution page in the Core Handbook. |
|
👋 Thanks for your first Pull Request and for helping build the future of Gutenberg and WordPress, @kraftbj! In case you missed it, we'd love to have you join us in our Slack community. If you want to learn more about WordPress development in general, check out the Core Handbook full of helpful information. |
54110e5 to
1984ba9
Compare
ObliviousHarmony
left a comment
There was a problem hiding this comment.
Thanks @kraftbj!
One of the things that stands out after looking over this pull request is that we have a lot of statically configurable port numbers. While this might be necessary in some cases, such as E2E tests (arguable), I think that the majority of developers just want something that reliably works without any extra steps. This is an awesome idea and solves a genuine problem we have with having static ports in .wp-env.json files.
We actually have some prior art with the automatic assignment of the mysql service when the port is set to null in the configuration file (the default). How would you feel about replicating this behavior with all of the other configurable ports? I feel like that might be a more straightforward developer experience.
Out of curiosity, is there some way to rely on Docker's ability to bind to unspecified host ports? We're actually already relying on that behavior with the mysql port when it's set to null. It's probably more trouble than it's worth since it would require altering config parsing (we append the port to certain options) but it's worth asking!
ObliviousHarmony
left a comment
There was a problem hiding this comment.
Alrighty @kraftbj, I've done a more thorough review and tested out the feature, it works great!
Aside from the comments I've left, I'm still not sure about this being entirely automatic. I really like the way that "mysqlPort": null causes a port to be assigned and it makes sense as a pattern.
What would you think of us having "{port-property}": null automatically assign a port rather than having the behavior be standard? Assuming we go that route, I'd also suggest we change the automatic mysql service port assignment to use this logic rather than having it rely on Docker for an automatic port.
You've added the WP_ENV_AUTO_PORT to disable this functionality (so that things like E2E tests don't break), but in practice, you probably always want the failure if a static port is bound. With the above, you'd be strongly encouraged to use automatic ports unless you need a static one.
|
Thanks for the thorough review! I've addressed the code-level feedback (loop refactor, structured port data instead of string messages, mocked tests, moving resolution into config parsing, dropping MySQL auto-resolution since it already works via Docker). Working through the The existing Given that, I think auto-by-default is the right call for the common case. The whole motivation behind #49843 is that developers run That said, I get the appeal of consistency with the The trade-off is that default ports become unpredictable (you'd get whatever's free instead of always 8888). For most developers that's fine -- they just want it to work. For anyone who needs a stable port (scripts, bookmarks, etc.), setting Thoughts on that approach? |
Yeah, I guessed that this was the motivation. It's a more common one nowadays, and indeed other colleagues have complained about poor ergonomics when trying to play with parallel "agents" working in different branches+envs. This weekend I was goofing around with a tool for myself to take advantage of git worktrees with wp-env more seamlessly. I hope I can refine it this next weekend, but in meantime here's the relevant part: allocating a free port when creating a new worktree: # ...
if ! test -d "$WORKTREE"; then
# Need to set up a fresh worktree, but first ensure we find an available
# HTTP port.
if ! find "$WORKTREES" -path '*/.wp-env.override.json' | grep -q .; then
PORT=9000
else
PORT=$(comm -13 \
<(find "$WORKTREES" -path '*/.wp-env.override.json' -print0 \
| xargs -0 jq .port | sort -g) \
<(seq 9000 9100) \
| grep -m1 .) || error "no ports available"
fi
# ...
git worktree add "$WORKTREE" "$BRANCH"
printf '{"port":%d,"phpmyadminPort":null}\n' "$PORT" > "$WORKTREE/.wp-env.override.json"
fi
cd "$WORKTREE"
# ... etc.There's a bit of shell magic there (command substitution, my old friend Anyway, this isn't meant to dismiss the need for
|
Indeed, I think that captures my immediate thoughts on this topic, too. For large feature development (e.g. media library work), I want a long-lived local dev environment as I've got things set up exactly as I want for that feature (100s of media items, large posts containing dozens of gallery blocks, etc, etc) Whereas for quick bug fixes and testing PRs, I'll want an ephemeral environment. Personally I like the idea of the default being the long-lived environment, and I like the general idea of the |
|
I can dig the opt-in approach. I hadn't used I've updated the branch to use
Test reproduction instructionsPrerequisites
# Block a port (replace PORT):
python3 -c "
import socket, time
s4 = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s4.bind(('0.0.0.0', PORT)); s4.listen(1)
s6 = socket.socket(socket.AF_INET6, socket.SOCK_STREAM)
s6.bind(('::', PORT)); s6.listen(1)
print('Blocking PORT'); import sys; sys.stdout.flush()
while True: time.sleep(1)
" &Group 1: Default behavior (no
|
I wouldn't say that But the mainstream usage so far was to spin up the instance once and have it running long-term. What changes all the time is not the instance itself, but the plugin code inside the mapped directories. Gutenberg, CIAB Admin, Woo plugins etc. Now, driven mainly with agents, the mainstream use case is shifting towards having multiple more ephemeral instances. I did a little research on how Vite behaves when running a dev server. It's a tool with very similar purpose and problems.
Based on this, I think the following behavior of
In Vite, the |
From that and @youknowriad's thumbs up on the comment, I'll make changes to match this. |
|
Changes in this push:
All previous tests were rerun and still pass. Updated/new test results:
Full test planPort blocker helper (blocks both IPv4+IPv6 on macOS)python3 -c "
import socket, time
s4 = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s4.bind(('0.0.0.0', PORT)); s4.listen(1)
s6 = socket.socket(socket.AF_INET6, socket.SOCK_STREAM)
s6.bind(('::', PORT)); s6.listen(1)
while True: time.sleep(1)
" &Group 1: Default Behavior (no --auto-port)
Group 2:
|
| Test | Description | Expect |
|---|---|---|
| 2.1 | Ports free | Port 8888 (preferred) |
| 2.2 | 8888 busy | Port 8889 (incremental) |
| 2.3 | Both 8888+8889 busy | Port 8890 for dev, 8891 for tests (incremental) |
| 2.4 | Explicit port 9200 in config, busy | Auto-resolves to 9201 |
| 2.5 | port: null in config |
Validation error (null no longer valid) |
Group 3: WordPress Integration
| Test | Description | Expect |
|---|---|---|
| 3.1 | siteurl/home on default port | Matches resolved port |
| 3.2 | siteurl/home with fallback port | Matches incremental port (e.g. 8889) |
Group 4: Edge Cases
| Test | Description | Expect |
|---|---|---|
| 4.1 | Restart while running | Port shifts (known behavior) |
| 4.2 | testsEnvironment: false | No tests port |
| 4.3 | --auto-port on non-start cmd | Silently ignored |
Group 5: Unit Tests
npm run test:unit -- --testPathPattern="packages/env"
Expect: 18 suites, 145 tests, 8 snapshots all passing
Group 6: JSON Config autoPort
| Test | Description | Expect |
|---|---|---|
| 6.1 | "autoPort": true in .wp-env.override.json, 8888 busy |
Auto-resolves to 8889 (no CLI flag needed) |
| 6.2 | "autoPort": true in config, --no-auto-port CLI override |
Docker bind error (CLI wins) |
| 6.3 | CI=true env var with "autoPort": true |
Docker bind error (CI suppresses auto-port) |
jsnajdr
left a comment
There was a problem hiding this comment.
Thanks for making the changes, I think this is mergeable now, it's a nice enhancement for wp-env.
There is a CI failure in the Static analysis on Window task. That looks strange and unrelated. Something with npm install. Maybe a rebase would help?
When wp-env starts, it now checks if the configured ports (default 8888/8889) are available. If a port is busy, it automatically finds an available alternative and displays a message to the user. New configuration options: - portRangeMin / portRangeMax: Constrain port selection for development - testsPortRangeMin / testsPortRangeMax: Constrain port selection for tests These can also be set via environment variables: - WP_ENV_PORT_RANGE_MIN / WP_ENV_PORT_RANGE_MAX - WP_ENV_TESTS_PORT_RANGE_MIN / WP_ENV_TESTS_PORT_RANGE_MAX
Remove all portRange config options (portRangeMin, portRangeMax, testsPortRangeMin, testsPortRangeMax, and their mysql/phpmyadmin counterparts) and always fall back to the ephemeral port range (49152-65535) when configured ports are busy.
When WP_ENV_AUTO_PORT=false, wp-env will fail with a clear error if a configured port is busy instead of silently selecting a different one. This is needed in CI where Playwright expects predictable ports. Disable auto-selection in the E2E workflow so that port conflicts surface as immediate failures rather than breaking Playwright's hardcoded webServer port check.
The preferred port (e.g. 8888) was being skipped because the range check required it to be within 49152-65535. Now the preferred port is always tried first regardless of the fallback range bounds.
Replace real port binding (net.createServer) with jest mocks to prevent flakiness and improve test speed.
- Rewrite resolve-available-ports.js with a loop over PORT_DEFINITIONS
instead of repetitive copy-paste blocks
- Store port changes as structured data ({configPath, from, to})
instead of pre-formatted strings
- Remove MySQL from auto-resolution since it already supports
Docker-native auto-assignment via mysqlPort: null
- Move port resolution into postProcessConfig, after environment
merging but before URL assignment, so WP_SITEURL/WP_HOME get
correct ports automatically without manual fixes
- Format port change messages at the display layer in docker/index.js
Update post-process-config tests to use async/await and rejects.toThrow for the now-async postProcessConfig. Update config-integration snapshots for new portChanges field.
Null ports auto-resolve (trying 8888/8889 first, falling back to ephemeral range). Explicit integer ports use strict mode and fail if busy. This replaces the WP_ENV_AUTO_PORT env var.
phpmyadminPort should auto-fallback when busy, matching previous behavior. Strict mode (fail if busy) only applies to the main port property where null-vs-explicit is the API contract.
When the configured port is null (auto-resolve default), use findAvailablePort to pick an open port instead of hardcoding 8888. Persist the resolved port to a file so getStatus() can read it across CLI invocations.
…erFiles Move port resolution from Docker's initConfig into start.js so both Docker and Playground runtimes receive a fully resolved config. Split the old initConfig into writeDockerFiles (for the start path) and loadDockerConfig (for non-start commands), eliminating the redundant second loadConfig call during start.
The non-start Docker methods (stop, clean, run, logs, getStatus) already receive a fully loaded config from the command layer. Remove the redundant loadDockerConfig that re-called loadConfig, replacing it with a simple ensureDockerInitialized guard that only checks the work directory exists.
Query the real public ports from running WordPress and tests containers via docker-compose port instead of reading config values that may be null with auto-resolved defaults.
With ports now defaulting to null (auto-resolve), port reassignment is expected behavior. The portChanges mechanism that warned about busy ports is no longer needed.
Restore default ports (8888/8889) so wp-env start uses fixed ports by default, matching pre-existing behavior. Port auto-resolution now only runs when --auto-port is passed. Also removes unused debug parameter from the Playground runtime start method.
|
I was hoping a rerun would resolve (can't do a rerun myself), but I rebased it to see if that would do it. |
|
The unit test failure ( |
What?
See #49843
Adds an opt-in
--auto-portflag towp-env startthat automatically finds available ports when configured ports are busy.Why?
The original implementation in this PR made auto-port resolution the default by changing port defaults to
null. Reviewer feedback requested this be opt-in instead, since unconditional port checking broke PHP unit tests (the port resolver threw "port busy" errors at config time) and changed long-standing default behavior.How?
8888/8889(matching pre-PR behavior)--auto-portCLI flag to thestartcommand (default:false)portResolveris only created when--auto-portis passed; otherwisenull, sopostProcessConfigskips port resolution entirelydebugparameter from the Playground runtimestartmethod (fixes lint error)Without
--auto-port, behavior is identical to trunk: Docker handles port binding directly and reports errors if ports are busy.Testing Instructions
npm run test:unit -- packages/env— all 138 tests passnpm run lint:js -- packages/env— no lint errorswp-env startwithout--auto-port— should use fixed ports 8888/8889, Docker-level error if busynpx http-server -p 8888wp-env start --auto-port— should find an available port and display a messageTesting Instructions for Keyboard
N/A — CLI tool, no UI changes.
Screenshots or screencast
N/A — CLI output changes only.