Skip to content

Add envlite tool#35

Open
sirreal wants to merge 73 commits into
trunkfrom
add-envlite-tool
Open

Add envlite tool#35
sirreal wants to merge 73 commits into
trunkfrom
add-envlite-tool

Conversation

@sirreal
Copy link
Copy Markdown
Owner

@sirreal sirreal commented May 8, 2026

Adds tools/local-env/envlite.php: a single-file PHP setup tool that takes a clean wordpress-develop checkout to a runnable state — WordPress on SQLite served by php -S, plus a green phpunit --group html-api — without Docker, MAMP, or system MySQL.

Existing local environments (Docker, MAMP) require background services and several minutes of setup. envlite uses only host PHP, node, and composer — tools already required for other dev workflows — and leaves no daemons behind.

Usage

php tools/local-env/envlite.php <subcommand>:

  • init [--port=N] [--no-build] — runs all 9 setup phases (preflight, port discovery, npm ci, npm run build:dev, composer install, SQLite drop-in, wp-tests-config.php, src/wp-config.php, router.php).
  • serve — runs php -S 127.0.0.1:<cached-port> -t src router.php in the foreground.
  • clean — removes envlite-managed files (manifest-tracked).
  • --force — disables interactive prompts (for CI).

Design invariants

  • Single PHP file. No library dependencies; all I/O, hashing, HTTP, and zip extraction via PHP's standard library.
  • No shell. Subprocesses spawned via proc_open with array commands. No sed/awk/curl/unzip/shasum dependencies.
  • Manifest-anchored ownership. Every file envlite writes is recorded in .envlite/manifest with its content hash. Re-runs silently re-stamp envlite-owned files; drift or unowned files prompt before overwriting (--force bypasses).
  • Atomic writes. Hash in-memory bytes, write to .tmp, fsync (when available), rename. Manifest never reads the renamed target — it stores the hash that was written.
  • LF only, no BOM. Hardcoded "\n", never PHP_EOL. Content hashes are byte-identical across platforms.
  • Pinned SQLite plugin. SHA256 of the wordpress.org zip is pinned; mismatches abort.

Tests

php tools/local-env/tests/run.php — 61 tests across 16 test files. No PHPUnit dependency (envlite bootstraps PHPUnit, so the test suite uses a tiny in-tree harness instead). Pure helpers tested directly; an end-to-end smoke drives Phases 5–8 + clean against a fixture directory.

Layout

  • tools/local-env/envlite.php — the tool (~850 lines)
  • tools/local-env/tests/ — test harness + 16 test files
  • plans/ENVLITE_SPECIFICATION.md — design spec
  • docs/superpowers/plans/2026-05-08-envlite.md — implementation plan

Trac ticket:

Use of AI Tools

AI assistance: Yes
Tool(s): Claude Code
Model(s): Claude Opus 4.7
Used for: Specification writing, implementation plan, and per-task implementation with two-stage review (spec compliance + code quality) per task. Output reviewed and edited by me.


This Pull Request is for code review only. Please keep all other discussion in the Trac ticket. Do not merge this Pull Request. See GitHub Pull Requests for Code Review in the Core Handbook for more details.

gemini-code-assist[bot]

This comment was marked as outdated.

@sirreal sirreal marked this pull request as ready for review May 8, 2026 14:19
@github-actions
Copy link
Copy Markdown

github-actions Bot commented May 8, 2026

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 props-bot label.

Unlinked Accounts

The following contributors have not linked their GitHub and WordPress.org accounts: @lucatume, @gemini-code-assist.

Contributors, please read how to link your accounts to ensure your work is properly credited in WordPress releases.

Core Committers: Use this line as a base for the props when committing in SVN:

Props sergeybiryukov, jonsurrell.

To understand the WordPress project's expectations around crediting contributors, please review the Contributor Attribution page in the Core Handbook.

This comment was marked as outdated.

the final state but breaks the "internally consistent at every
step" invariant.

Phases 1, 2, 4, 5, 6 are mutually independent and could be run in
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Adding note/comment as a TODO: I think there are gains to be had here by making the two main HTTP based tools (Composer and npm) run in parallel.
To verify and decide whether the 5s seconds savings is real or not. If real, fine as is, else I would explore parallelization.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

👍

I ran a benchmark to see whether running npm ci and composer install was likely to improve things or not. My machine does should paralell as 1.3 times faster than sequential.

sirreal added a commit that referenced this pull request May 8, 2026
- Keep within the spec's PHP 7.4 floor: replace str_starts_with /
  str_contains / str_ends_with calls in envlite.php and the tests with
  strpos/substr equivalents. The preflight gates on PHP_VERSION_ID >=
  70400, but the rest of the file used PHP 8.0+ helpers and would fatal
  before reaching preflight.

- Convert phase 5-8 failures into the spec's diagnostic format. Phases
  throw RuntimeException on SHA256 mismatch, missing placeholders, and
  I/O errors; init now wraps each phase in envlite_init_phase_guard,
  which logs `envlite init: phase N: <cause>` and exits 1.

- Handle file_get_contents() returning false in phases 5/6/7. Required
  reads (db.copy, sample configs) raise a structured `phase N: cannot
  read <path>` failure; optional current-file reads short-circuit
  cleanly so the owned_drifted branch never hashes null. The
  observe_ht_sqlite step skips on read failure rather than recording
  the empty-string hash.

- Align preflight npm floor with package.json engines (>= 10.2.3 vs
  the previous 10.2.0) so preflight catches what `npm ci` would later.
  Spec text updated to match.

- Correct the plan note that claimed no edits to package.json or
  .gitignore — both are touched in this branch.
exception. The `--group html-api` subset still passes clean on PHP
8.5.5 against the SQLite drop-in. Other groups may surface
deprecations; that's a per-group fix, not envlite's problem.
3. **No `composer.lock`, by upstream design.** Every Phase 4 run
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Review: should this run with PHP 7.4 platform?
Pro: it would guarantee same dependencies no matter the PHP version used.
Con: it would ignore the PHP version used to run the command.
Not sure what should be the default here. A developer/agent might not be making a conscious decision to use a specific PHP version to run the command, but rather jut run whatever is available.

5. **Two distinct config files.** `wp-tests-config.php` (Phase 6) and
`src/wp-config.php` (Phase 7) are loaded by different bootstrap paths
and serve different purposes. Both are needed; do not consolidate.
6. **Pin the plugin SHA, not the version number.** Plugin version
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

This requires updating this repository to get the latest version of a plugin that is not part of Core WordPress, but just a utility for it.
I think the version should be stable, but latest so that someone running envlite would get the greatest and latest version of the plugin for local development.


## What envlite explicitly does NOT do

- Allocate ports for *external* tooling (database GUIs, Xdebug, etc.) —
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

I think being deterministic about the XDebug port would really help when working on multiple environments at the same time: all calling the same XDebug port would make debugging impossible.

@sirreal

This comment was marked as resolved.

sirreal added a commit that referenced this pull request May 8, 2026
- Keep within the spec's PHP 7.4 floor: replace str_starts_with /
  str_contains / str_ends_with calls in envlite.php and the tests with
  strpos/substr equivalents. The preflight gates on PHP_VERSION_ID >=
  70400, but the rest of the file used PHP 8.0+ helpers and would fatal
  before reaching preflight.

- Convert phase 5-8 failures into the spec's diagnostic format. Phases
  throw RuntimeException on SHA256 mismatch, missing placeholders, and
  I/O errors; init now wraps each phase in envlite_init_phase_guard,
  which logs `envlite init: phase N: <cause>` and exits 1.

- Handle file_get_contents() returning false in phases 5/6/7. Required
  reads (db.copy, sample configs) raise a structured `phase N: cannot
  read <path>` failure; optional current-file reads short-circuit
  cleanly so the owned_drifted branch never hashes null. The
  observe_ht_sqlite step skips on read failure rather than recording
  the empty-string hash.

- Align preflight npm floor with package.json engines (>= 10.2.3 vs
  the previous 10.2.0) so preflight catches what `npm ci` would later.
  Spec text updated to match.

- Correct the plan note that claimed no edits to package.json or
  .gitignore — both are touched in this branch.
@sirreal sirreal force-pushed the add-envlite-tool branch from 2aae842 to 0a45e19 Compare May 8, 2026 21:01
@sirreal

This comment was marked as resolved.

sirreal added 28 commits May 12, 2026 09:13
Currently `phpunit` and `envlite serve` share `.ht.sqlite`, so the
test bootstrap's `install.php` drops the dev site's tables on every
test run. Design proposes a single `define( 'DB_FILE', '.ht.test.sqlite' )`
in Phase 6's `wp-tests-config.php`, leaving Phase 7 and the live
runtime untouched.
Bite-sized tasks for the design at plans/2026-05-09-envlite-test-db-
isolation-design.md: pre-implementation verification, tripwire,
DB_FILE append, end-to-end manual test, spec update.
Inline code spans render backslashes literally; ['\"] would show as
['\"] not ['"] in the rendered Markdown.
The fixture used 9999, which falls inside the 1..65535 range that
envlite_phase1_discover_port treats as a valid cached port — so the
function returned 9999 and the assertion that the port came from the
8100..8899 auto-discovery pool would have failed if the suite ever
got that far. Use 70000 so the cache-out-of-range path is actually
exercised.
ZipArchive::extractTo returns false on a partial or failed extraction
(permissions, full disk, malformed entries). The previous code ignored
that and unconditionally recorded the plugin directory as envlite-owned;
on the next run the db.copy short-circuit would then skip re-downloading
and leave a half-extracted, unverified SQLite plugin tree in place.
Treat a non-true return as fatal so Phase 5 surfaces the failure
instead of pinning broken state into the manifest.
api.wordpress.org/secret-key/1.1/salt/ returns random bytes that can
contain \`\$\` and \`\\\`. Passing those bytes as preg_replace's
replacement argument means sequences like \`\$1\`, \`\\1\`, or \`\$&\`
get interpreted as backreferences and silently corrupt the salts that
land in src/wp-config.php. Switch to preg_replace_callback so the
salts block is inserted verbatim regardless of what characters it
contains.

Pinned by a regression test that feeds backreference-shaped bytes
through the render path and asserts they survive verbatim.
wp-config-sample.php ships CRLF in tree, so the previous render path
mixed the sample's CRLF lines with envlite's LF-only injections
(WP_HOME, WP_SITEURL, the fetched salts block). The rendered file is
then ugly and — worse — the manifest hash recorded for src/wp-config.php
becomes sensitive to whatever EOL conversion the user's git client
applied at checkout time, which would spuriously trip envlite's drift
prompt on machines with different settings.

Strip \\r\\n to \\n once at the top of envlite_phase7_render so output
is LF-only regardless of how the sample was checked out. Pinned by a
regression test asserting the rendered config contains no \\r\\n.
…able

php -S does not honor Apache .ht* deny rules, so a request for
/wp-content/database/.ht.sqlite would be served as a static binary by
the existing-file passthrough.
…index.php

Existing directories such as /wp-admin/ previously fell through to the
front controller, returning the front-end response instead of executing
src/wp-admin/index.php. Return false for directories with an index.php
so php -S serves the directory index. Directories without an index
still fall through to the front controller to avoid php -S's default
directory listings.
After a reboot or other session change, the cached port in
.envlite/port may now be held by another process. Phase 1 returned
the cached port without verifying it was free, so the failure only
surfaced at bind time in `serve` — after every other init phase had
already run. Verify the cached port is free; on conflict fall through
to the seeded auto-discovery loop and re-cache the new pick.
@sirreal sirreal force-pushed the add-envlite-tool branch from 520a5dc to 9242044 Compare May 12, 2026 07:13
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants