Skip to content

feat(cli): add wordpress-source command to materialize core source from the archive cache - #2566

Merged
chubes4 merged 1 commit into
mainfrom
feat/wordpress-source-command
Oct 11, 2026
Merged

chubes4 merged 1 commit into
mainfrom
feat/wordpress-source-command

Conversation

@chubes4

@chubes4 chubes4 commented Oct 11, 2026

Copy link
Copy Markdown
Collaborator

What

Adds wp-codebox wordpress-source [--wp <version-query>] [--json]. It writes WordPress core source files to the host from the same validated archive cache WP Codebox uses to boot Playground.

  • packages/runtime-playground/src/playground-wordpress-source.ts: materializePlaygroundWordPressSource(). Exported from the playground package.
  • packages/cli/src/commands/wordpress-source.ts: the CLI command. Registered in the command router and CLI entry, and documented in the CLI help and the README command list.
  • @php-wasm/stream-compression (already in the lockfile at 3.1.46 through @php-wasm/universal) is now a direct dependency of runtime-playground, used for zip extraction. The command does not shell out to unzip.

Why

Some tools need WordPress core source without a booted site. One example is PHP smoke tests that load wp-includes/html-api/*. With this command they use the same WordPress that WP Codebox runs. WP Codebox stays the only owner of WordPress release resolution, download and validation, so consumers don't need their own downloader.

Contract

wp-codebox wordpress-source [--wp <version-query>] [--json]
  • --wp takes an exact version (7.1.2) or latest. If omitted, it defaults to latest. The query goes through the existing resolvePlaygroundWordPressStartupAsset → resolveWordPressReleaseForStartup path, which ensures a validated ${version}.zip in playgroundWordPressArchiveCacheDirectory(). It uses the existing lock, downloader and validator; there is no second downloader. trunk, nightly and URL queries are rejected because they don't map to a release archive whose $wp_version can be checked.

  • The archive is extracted once to <archive cache>/source/<version>/wordpress, the directory that contains wp-includes/version.php.

  • Extraction is atomic:

    1. Files are extracted into a sibling directory, <archive cache>/source/.<version>.<pid>.<uuid>.partial.
    2. Zip entries that would escape that directory are rejected.
    3. The archive's single top-level directory is used as the root (e.g. wordpress/).
    4. wp-includes/version.php must declare $wp_version equal to the resolved version.
    5. The staged directory is then renamed to <archive cache>/source/<version>.

    If another caller has already published a tree that verifies, it is reused. A tree that fails verification is moved aside and replaced. The staging directory is always removed.

  • With --json, stdout is exactly one line: {"schema":"wp-codebox/wordpress-source/v1","version":"7.1.2","root":"…/source/7.1.2/wordpress","archive":"…/7.1.2.zip","sourceUrl":"https://…"}.

  • Without --json, stdout is only the root path.

  • Diagnostics go to stderr.

  • Exit codes: 0 on success; 1 when materialization fails (offline, unknown version, invalid archive, verification mismatch), with a message on stderr and nothing on stdout; 2 for invalid arguments.

Verification

  • tests/wordpress-source.test.ts runs in the fast lane via smoke discovery. It covers:
    • a cache hit producing the full layout and JSON shape
    • version.php verification, including mismatch and missing file
    • reuse of an existing verified tree without re-extraction
    • archives with no top-level directory
    • path-traversal rejection
    • the atomic failure path: no final directory and no leftover .partial directories
    • 4 concurrent callers ending up with one tree
    • replacement of an unverifiable final tree
    • download on a cache miss, through the existing downloader with fetch stubbed
    • an offline download and an invalid archive download
    • rejection of trunk
    • CLI behavior: --json emits one line with exactly the 5 keys; plain output is the root path; failures leave stdout empty, write to stderr and exit 1; bad flags exit 2
  • npm run check: passed, 329 commands. This covers the build, the typecheck (tsc -b) and every fast contract, including the new test.
  • Real end-to-end run on the default cache:
    • npm run build && node packages/cli/dist/index.js wordpress-source --wp 7.1.2 --json exits 0 and prints {"schema":"wp-codebox/wordpress-source/v1","version":"7.1.2","root":"$HOME/.wordpress-playground/source/7.1.2/wordpress","archive":"$HOME/.wordpress-playground/7.1.2.zip","sourceUrl":"https://wordpress.org/wordpress-7.1.2.zip"}.
    • <root>/wp-includes/html-api/class-wp-html-tag-processor.php exists, and version.php declares $wp_version = '7.1.2'.
  • Real run against a fresh empty cache (WP_CODEBOX_PLAYGROUND_WORDPRESS_CACHE_DIR=$(mktemp -d)):
    • The download path ran, and 3782 files were extracted, matching the 3782 file entries in the zip.
    • The html-api files and version.php have the same SHA-256 hashes as the output of unzip.
    • A second call reused the existing tree, and plain output printed only the root.
    • --json with no --wp resolved latest to 7.1.3.
    • --wp 0.0.1 exited 1 with stderr HTTP 404 Not Found. --wp trunk exited 1 with a clear message.
    • No .partial directories were left behind.

AI disclosure: written by Claude in OpenCode with Chris Huber; Claude implemented, tested and opened this PR.

@chubes4
chubes4 merged commit 600afda into main Oct 11, 2026
5 checks passed
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.

1 participant