Skip to content

Commit f1ec68b

Browse files
committed
Repair Cloudflare WordPress corpus dev rig
1 parent 21f1354 commit f1ec68b

8 files changed

Lines changed: 1182 additions & 75 deletions

package-lock.json

Lines changed: 407 additions & 45 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,7 @@
8282
"build": "node ./node_modules/typescript/bin/tsc -b packages/runtime-core packages/runtime-playground packages/cli && node scripts/ensure-cli-bin-executable.mjs",
8383
"cloudflare:dry-run": "npm exec -- wrangler deploy --dry-run --config packages/runtime-cloudflare/wrangler.jsonc",
8484
"cloudflare:local-gate": "node scripts/cloudflare-local-gate.mjs",
85+
"postinstall": "patch-package",
8586
"generate:cloudflare-wordpress-runtime-corpus": "tsx scripts/generate-cloudflare-wordpress-runtime-corpus.ts",
8687
"provision:cloudflare-wordpress-runtime-corpus": "node scripts/provision-cloudflare-wordpress-runtime-corpus.mjs",
8788
"prepare": "npm run build",
@@ -191,7 +192,6 @@
191192
"test:php-artifact-import-idempotency": "php scripts/php-artifact-import-idempotency-smoke.php",
192193
"test:php-browser-runtime-local-package": "php scripts/php-browser-runtime-local-package-smoke.php",
193194
"test:php-browser-runtime-agent-substrate": "php scripts/php-browser-runtime-agent-substrate-smoke.php",
194-
"test:php-browser-preview-only-session": "php -d zend.assertions=1 -d assert.exception=1 scripts/php-browser-preview-only-session-smoke.php",
195195
"test:mcp-client-configs": "tsx tests/mcp-client-configs.test.ts",
196196
"test:runtime-tool-policy": "tsx tests/runtime-tool-policy.test.ts",
197197
"test:primitive-contract-parity": "tsx tests/primitive-contract-parity.test.ts",
@@ -256,6 +256,7 @@
256256
"devDependencies": {
257257
"@cloudflare/workers-types": "^5.20260718.1",
258258
"@types/node": "^24.0.0",
259+
"patch-package": "^8.0.1",
259260
"tsx": "^4.20.0",
260261
"wrangler": "^4.112.0"
261262
},
@@ -455,6 +456,7 @@
455456
"yargs-parser": "^21.1.1"
456457
},
457458
"overrides": {
459+
"@php-wasm/stream-compression": "3.1.45",
458460
"ajv": "^8.20.0",
459461
"express": "^4.22.2",
460462
"qs": "^6.15.2",

patches/@php-wasm+stream-compression+3.1.45.patch

Lines changed: 740 additions & 0 deletions
Large diffs are not rendered by default.

patches/README.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
# Temporary Playground Overlay
2+
3+
`@php-wasm+stream-compression+3.1.45.patch` is the built output of
4+
WordPress Playground PR [#4110](https://github.com/WordPress/wordpress-playground/pull/4110)
5+
at commit `afea9c042999c65f5b43bc049ca4b2bea9d8b361`.
6+
7+
WP Codebox needs the repaired bounded Range decoder for the Cloudflare dev rig
8+
and browser asset path before that upstream change can be merged and released.
9+
Remove this patch and the package override after a published Playground package
10+
contains the same change.

scripts/cloudflare-local-gate.mjs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { spawn } from "node:child_process"
22
import { mkdtemp, rm } from "node:fs/promises"
33
import { tmpdir } from "node:os"
44
import { join } from "node:path"
5+
import { stripVTControlCharacters } from "node:util"
56

67
const port = 8792
78
const origin = `http://127.0.0.1:${port}`
@@ -73,7 +74,7 @@ async function stopWorker() {
7374
async function waitForServer() {
7475
const deadline = Date.now() + 30_000
7576
while (Date.now() < deadline) {
76-
if (/Ready on http:\/\/(?:localhost|127\.0\.0\.1):8792/.test(output)) return
77+
if (/Ready on http:\/\/(?:localhost|127\.0\.0\.1):8792/.test(stripVTControlCharacters(output))) return
7778
if (child.exitCode !== null) throw new Error(`workerd exited before starting:\n${output}`)
7879
await new Promise((resolve) => setTimeout(resolve, 100))
7980
}

scripts/generate-cloudflare-wordpress-runtime-corpus.ts

Lines changed: 9 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,23 @@
11
import { createHash } from "node:crypto"
22
import { mkdir, writeFile } from "node:fs/promises"
33
import { dirname, resolve } from "node:path"
4-
import { decodeRemoteZip, encodeZip } from "@php-wasm/stream-compression"
4+
import { decodeZip, encodeZip } from "@php-wasm/stream-compression"
55
import { isWordPressRuntimeFile } from "../packages/runtime-cloudflare/src/wordpress-runtime-corpus.js"
66
import { WORDPRESS_RUNTIME_ARTIFACT_SCHEMA, wordpressRuntimeArtifactKey, type WordPressRuntimeArtifactManifest } from "../packages/runtime-cloudflare/src/wordpress-runtime-artifact.js"
77

88
const sourceUrl = process.env.WORDPRESS_RUNTIME_ARCHIVE_URL ?? "https://downloads.wordpress.org/release/wordpress-7.0.2.zip"
99
const sourceVersion = process.env.WORDPRESS_RUNTIME_VERSION ?? "7.0.2"
1010
const output = resolve(process.env.WORDPRESS_RUNTIME_ARTIFACT_OUTPUT ?? "artifacts/cloudflare-wordpress-runtime-corpus.zip")
1111
const manifestOutput = resolve("packages/runtime-cloudflare/assets/wordpress-runtime-artifact.json")
12-
const decoder = new TextDecoder()
13-
const archivePaths = new Set<string>()
14-
const selected: File[] = []
15-
16-
const response = await fetch(sourceUrl, { method: "HEAD" })
17-
if (!response.ok) throw new Error(`Unable to inspect WordPress archive: ${response.status}.`)
12+
const response = await fetch(sourceUrl)
13+
if (!response.ok || !response.body) throw new Error(`Unable to download WordPress archive: ${response.status}.`)
1814
const identity = response.headers.get("etag") ?? response.headers.get("last-modified") ?? undefined
19-
const stream = await decodeRemoteZip(sourceUrl, (entry) => {
20-
const path = decoder.decode(entry.path)
21-
archivePaths.add(path)
22-
return isWordPressRuntimeFile(path, archivePaths)
23-
})
24-
for await (const entry of stream) {
25-
const path = entry instanceof File ? entry.name : decoder.decode(entry.path)
26-
if (!isWordPressRuntimeFile(path, archivePaths)) continue
27-
const bytes = entry instanceof File ? new Uint8Array(await entry.arrayBuffer()) : entry.bytes
28-
selected.push(new File([bytes], path, { lastModified: 0 }))
29-
}
15+
const files: File[] = []
16+
for await (const file of decodeZip(response.body)) files.push(file)
17+
const archivePaths = new Set(files.map((file) => file.name))
18+
const selected = files
19+
.filter((file) => isWordPressRuntimeFile(file.name, archivePaths))
20+
.map((file) => new File([file], file.name, { lastModified: 0 }))
3021
selected.sort((left, right) => left.name.localeCompare(right.name))
3122
if (!selected.length) throw new Error("WordPress archive did not yield a runtime corpus.")
3223
const archive = new Uint8Array(await new Response(encodeZip(selected)).arrayBuffer())

tests/cloudflare-runtime.test.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -328,7 +328,9 @@ test("WordPress runtime corpus generator keeps the ZIP outside the Worker bundle
328328
const generator = await readFile(new URL("../scripts/generate-cloudflare-wordpress-runtime-corpus.ts", import.meta.url), "utf8")
329329
const artifact = await readFile(new URL("../packages/runtime-cloudflare/src/wordpress-runtime-artifact.ts", import.meta.url), "utf8")
330330
const manifest = JSON.parse(await readFile(new URL("../packages/runtime-cloudflare/assets/wordpress-runtime-artifact.json", import.meta.url), "utf8")) as WordPressRuntimeArtifactManifest
331-
assert.match(generator, /decodeRemoteZip\(sourceUrl/)
331+
assert.match(generator, /const response = await fetch\(sourceUrl\)/)
332+
assert.match(generator, /decodeZip\(response\.body\)/)
333+
assert.doesNotMatch(generator, /decodeRemoteZip/)
332334
assert.match(generator, /encodeZip\(selected\)/)
333335
assert.match(generator, /lastModified: 0/)
334336
assert.match(generator, /artifacts\/cloudflare-wordpress-runtime-corpus\.zip/)

tests/cloudflare-wordpress-archive-corpus.test.ts

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,18 @@
11
import assert from "node:assert/strict"
22
import test from "node:test"
3-
import { decodeRemoteZip, type CentralDirectoryEntry } from "@php-wasm/stream-compression"
3+
import { decodeZip } from "@php-wasm/stream-compression"
44
import { isWordPressRuntimeFile, summarizeWordPressRuntimeCorpus, type WordPressArchiveEntry } from "../packages/runtime-cloudflare/src/wordpress-runtime-corpus.js"
55

66
const WORDPRESS_ARCHIVE_URL = "https://wordpress.org/latest.zip"
77
const MAX_RUNTIME_CORPUS_BYTES = 32 * 1024 * 1024
88

99
test("WordPress production runtime corpus stays within the Worker materialization budget", async () => {
10-
const decoder = new TextDecoder()
1110
const entries: WordPressArchiveEntry[] = []
12-
const stream = await decodeRemoteZip(WORDPRESS_ARCHIVE_URL, (entry) => {
13-
const directoryEntry = entry as CentralDirectoryEntry
14-
entries.push({ path: decoder.decode(directoryEntry.path), uncompressedSize: directoryEntry.uncompressedSize, isDirectory: directoryEntry.isDirectory })
15-
return false
16-
})
17-
for await (const _ of stream) {
18-
// The predicate records central-directory metadata and selects no file ranges.
11+
const response = await fetch(WORDPRESS_ARCHIVE_URL)
12+
assert.equal(response.ok, true, `WordPress archive returned HTTP ${response.status}.`)
13+
assert.ok(response.body, "WordPress archive response has no body.")
14+
for await (const file of decodeZip(response.body)) {
15+
entries.push({ path: file.name, uncompressedSize: file.size, isDirectory: file.name.endsWith("/") })
1916
}
2017

2118
const corpus = summarizeWordPressRuntimeCorpus(entries)
@@ -39,5 +36,7 @@ test("WordPress production runtime corpus stays within the Worker materializatio
3936
legacyBytes: legacy.reduce((total, entry) => total + entry.uncompressedSize, 0),
4037
largestExcludedGroups: byGroup(excluded),
4138
}))
39+
assert.ok(entries.some((entry) => entry.path === "wordpress/wp-includes/version.php"), "Archive is missing wp-includes/version.php.")
40+
assert.ok(corpus.selectedFiles > 0, "WordPress archive did not yield a runtime corpus.")
4241
assert.ok(corpus.selectedBytes <= MAX_RUNTIME_CORPUS_BYTES, `Selected ${corpus.selectedBytes} bytes exceeds the ${MAX_RUNTIME_CORPUS_BYTES}-byte runtime corpus budget.`)
4342
})

0 commit comments

Comments
 (0)