Skip to content

Commit e44ec70

Browse files
authored
fix: force fresh dist for release packages (#2000)
Force release packaging to regenerate TypeScript output and verify shipped readonly mount isolation and binary-safe materialization. Fixes #1998.
1 parent 96f2c00 commit e44ec70

3 files changed

Lines changed: 86 additions & 11 deletions

File tree

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,7 @@
8585
],
8686
"scripts": {
8787
"build": "node ./node_modules/typescript/bin/tsc -b packages/runtime-core packages/runtime-playground packages/cli && node scripts/ensure-cli-bin-executable.mjs",
88+
"build:release": "node ./node_modules/typescript/bin/tsc -b --force packages/runtime-core packages/runtime-playground packages/cli && node scripts/ensure-cli-bin-executable.mjs",
8889
"cloudflare:dry-run": "npm exec -- wrangler deploy --dry-run --config packages/runtime-cloudflare/wrangler.jsonc",
8990
"cloudflare:dry-run:d1": "npm exec -- wrangler deploy --dry-run --config packages/runtime-cloudflare/wrangler.d1.jsonc",
9091
"cloudflare:local-gate": "node scripts/cloudflare-local-gate.mjs",

scripts/package-release-artifact.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ const artifactPath = resolve(repoRoot, "dist", artifactName)
2020
const recursiveRmOptions = { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }
2121

2222
try {
23-
await execFileAsync("npm", ["run", "build"], { cwd: repoRoot, maxBuffer: 1024 * 1024 * 10 })
23+
await execFileAsync("npm", ["run", "build:release"], { cwd: repoRoot, maxBuffer: 1024 * 1024 * 10 })
2424

2525
await mkdir(packageRoot, { recursive: true })
2626

tests/release-package-coverage.test.ts

Lines changed: 84 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
import assert from "node:assert/strict"
22
import { execFile } from "node:child_process"
3-
import { cp, lstat, mkdir, mkdtemp, readFile, rm } from "node:fs/promises"
3+
import { cp, lstat, mkdir, mkdtemp, readFile, rm, writeFile } from "node:fs/promises"
44
import { tmpdir } from "node:os"
55
import { join, resolve } from "node:path"
6+
import { pathToFileURL } from "node:url"
67
import { promisify } from "node:util"
78

89
const execFileAsync = promisify(execFile)
@@ -18,15 +19,25 @@ assert.deepEqual(homeboy.release?.package_coverage, [{
1819
archive_root: "wp-codebox",
1920
}])
2021

21-
const { stdout } = await execFileAsync("npm", ["run", "release:package"], {
22-
cwd: repositoryRoot,
23-
env: {
24-
...process.env,
25-
WP_CODEBOX_RELEASE_PLATFORM: "linux",
26-
WP_CODEBOX_RELEASE_ARCH: "x64",
27-
},
28-
maxBuffer: 1024 * 1024 * 20,
29-
})
22+
await execFileAsync("npm", ["run", "build"], { cwd: repositoryRoot, maxBuffer: 1024 * 1024 * 10 })
23+
const staleDistPath = resolve(repositoryRoot, "packages/runtime-playground/dist/mount-materialization.js")
24+
const currentDist = await readFile(staleDistPath)
25+
await writeFile(staleDistPath, "export const staleBuild = true\n")
26+
27+
let stdout = ""
28+
try {
29+
({ stdout } = await execFileAsync("npm", ["run", "release:package"], {
30+
cwd: repositoryRoot,
31+
env: {
32+
...process.env,
33+
WP_CODEBOX_RELEASE_PLATFORM: "linux",
34+
WP_CODEBOX_RELEASE_ARCH: "x64",
35+
},
36+
maxBuffer: 1024 * 1024 * 20,
37+
}))
38+
} finally {
39+
await writeFile(staleDistPath, currentDist)
40+
}
3041
const artifacts = JSON.parse(stdout.trim().split("\n").at(-1) ?? "[]")
3142
assert.equal(artifacts.length, 2, "release package emitted unexpected artifacts")
3243
assert.deepEqual(artifacts.filter((artifact: { type: string }) => artifact.type === "wordpress-plugin-zip"), [
@@ -95,6 +106,8 @@ try {
95106
env: { ...process.env, WP_CODEBOX_NODE_BIN: "" },
96107
})
97108
assert.equal(wrapperVersion, version, "wrapper must fall back to host Node when bundled Node is incompatible")
109+
110+
await assertPackagedReadonlyMaterialization(root)
98111
}
99112
} finally {
100113
await rm(extractionRoot, { recursive: true, force: true })
@@ -139,3 +152,64 @@ try {
139152
}
140153

141154
console.log("release package coverage passed")
155+
156+
async function assertPackagedReadonlyMaterialization(root: string): Promise<void> {
157+
const modulePath = join(root, "packages", "runtime-playground", "dist", "mount-materialization.js")
158+
const runtime = await import(pathToFileURL(modulePath).href) as {
159+
stageReadonlyPlaygroundMounts(mounts: Array<Record<string, unknown>>): Promise<{
160+
mounts: Array<{ source: string }>
161+
[Symbol.asyncDispose](): Promise<void>
162+
}>
163+
materializePlaygroundStagedInputs(server: unknown, mounts: Array<Record<string, unknown>>): Promise<{ materialized: number }>
164+
}
165+
assert.equal(typeof runtime.stageReadonlyPlaygroundMounts, "function", "packaged dist must contain readonly mount isolation")
166+
assert.equal(typeof runtime.materializePlaygroundStagedInputs, "function", "packaged dist must contain staged input materialization")
167+
168+
const fixtureRoot = await mkdtemp(join(tmpdir(), "wp-codebox-packaged-readonly-"))
169+
const readonlySource = join(fixtureRoot, "font.woff2")
170+
const binarySource = join(fixtureRoot, "translation.mo")
171+
const readonlyBytes = Buffer.from("774f4632000100000000108000120000", "hex")
172+
const binaryBytes = Buffer.from("de12049500000000c50000001c000000", "hex")
173+
try {
174+
await writeFile(readonlySource, readonlyBytes)
175+
await writeFile(binarySource, binaryBytes)
176+
177+
const staging = await runtime.stageReadonlyPlaygroundMounts([{
178+
type: "file",
179+
source: readonlySource,
180+
target: "/wordpress/wp-content/themes/example/font.woff2",
181+
mode: "readonly",
182+
}])
183+
try {
184+
await writeFile(staging.mounts[0].source, Buffer.from("sandbox overwrite"))
185+
assert.deepEqual(await readFile(readonlySource), readonlyBytes, "packaged readonly staging must isolate host bytes")
186+
} finally {
187+
await staging[Symbol.asyncDispose]()
188+
}
189+
190+
let directWrites = 0
191+
let materializedBase64 = ""
192+
const result = await runtime.materializePlaygroundStagedInputs({
193+
playground: {
194+
async writeFile() {
195+
directWrites++
196+
},
197+
async run({ code }: { code: string }) {
198+
const match = code.match(/\$payload = json_decode\((.*), true\);/)
199+
assert.ok(match, "packaged binary fallback includes its materialization payload")
200+
const payload = JSON.parse(JSON.parse(match[1])) as { directories?: string[]; files?: Array<{ contentsBase64?: string }> }
201+
if (code.includes("wp-codebox/host-mount-directory-materialization/v1")) {
202+
return { text: JSON.stringify({ schema: "wp-codebox/host-mount-directory-materialization/v1", created: payload.directories?.length ?? 0, skipped: 0 }) }
203+
}
204+
materializedBase64 = payload.files?.[0]?.contentsBase64 ?? ""
205+
return { text: JSON.stringify({ schema: "wp-codebox/host-mount-materialization/v1", materialized: 1, created: 0, skipped: 0 }) }
206+
},
207+
},
208+
}, [{ type: "file", source: binarySource, target: "/wordpress/wp-content/languages/example.mo", mode: "readonly" }])
209+
assert.equal(result.materialized, 1)
210+
assert.equal(directWrites, 0, "invalid UTF-8 bytes must bypass Playground's text writer")
211+
assert.equal(materializedBase64, binaryBytes.toString("base64"), "packaged binary fallback must preserve exact bytes")
212+
} finally {
213+
await rm(fixtureRoot, { recursive: true, force: true })
214+
}
215+
}

0 commit comments

Comments
 (0)