// @vitest-environment node import { createHash } from "node:crypto"; import { appendFile, cp, mkdtemp, readFile, rm, writeFile, } from "node:fs/promises"; import { tmpdir } from "node:os"; import path from "node:path"; import { afterEach, describe, expect, it } from "vitest"; import { buildPhpPack, PHP_ENGINE_LOCK, verifyPhpPack, } from "./php-engine-pack.mjs"; const temporaryDirectories: string[] = []; afterEach(async () => { await Promise.all( temporaryDirectories .splice(0) .map((directory) => rm(directory, { recursive: true, force: true })), ); }); async function digest(file: string): Promise { return createHash("sha256") .update(await readFile(file)) .digest("hex"); } describe("PHP engine-pack gate", () => { it("verifies the installed Asyncify PHP runtime and native smoke cases", async () => { await expect( verifyPhpPack(path.resolve("public", "engines", "php")), ).resolves.toEqual( expect.objectContaining({ engineVersion: "PHP 8.5.8 preg / PCRE2 10.44", platform: "wasm32-emscripten", source: expect.objectContaining({ runtimeVariant: "PHP 8.5 Asyncify web build (without intl extension)", nativeComponents: expect.objectContaining({ linkedComponentCount: 32, excludedDetectionCount: 7, }), }), licensing: expect.objectContaining({ nativeInventory: expect.objectContaining({ independentlyPinnedLegalAssets: 42, }), }), }), ); }); it("pins exact package, loader, and WebAssembly identities", () => { expect(PHP_ENGINE_LOCK.packageVersion).toBe("3.1.46"); expect(PHP_ENGINE_LOCK.phpVersion).toBe("8.5.8"); expect(PHP_ENGINE_LOCK.pcreVersion).toBe("10.44"); expect(PHP_ENGINE_LOCK.asyncMode).toBe("asyncify"); expect(PHP_ENGINE_LOCK.npm.web.integrity).toMatch(/^sha512-/u); expect(PHP_ENGINE_LOCK.npm.universal.integrity).toMatch(/^sha512-/u); expect(PHP_ENGINE_LOCK.sourceAssets.wasm).toHaveLength(64); expect(PHP_ENGINE_LOCK.sourceAssets.nativeInventory).toHaveLength(64); expect(Object.keys(PHP_ENGINE_LOCK.legalAssets)).toHaveLength(42); }); it("builds byte-identical closed packs from pinned npm inputs", async () => { const directory = await mkdtemp(path.join(tmpdir(), "regex-php-pack-")); temporaryDirectories.push(directory); const first = path.join(directory, "first"); const second = path.join(directory, "second"); await buildPhpPack({ output: first }); await buildPhpPack({ output: second }); for (const file of [ "php.wasm", "php-loader.mjs", "engine-metadata.json", "native-components.json", "SHA256SUMS", "LICENSE.Emscripten-4.0.19.txt", "LICENSE.llvm-compiler-rt.txt", "LICENSE.PHP-4.0.txt", "LICENSE.PHP-Zend-2.0.txt", "LICENSE.PCRE2-10.44.txt", "LICENSE.php-wasm-GPL-2.0-or-later.txt", "NOTICE.PHP-REDIST-BINS.txt", "PATENTS.libaom-3.12.1.txt", ]) { expect(await digest(path.join(first, file))).toBe( await digest(path.join(second, file)), ); } }, 30_000); it("anchors legal assets independently from the pack checksum manifest", async () => { const directory = await mkdtemp(path.join(tmpdir(), "regex-php-legal-")); temporaryDirectories.push(directory); const pack = path.join(directory, "pack"); await cp(path.resolve("public", "engines", "php"), pack, { recursive: true, force: false, errorOnExist: true, }); const legalName = "LICENSE.OpenSSL-1.1.1t.txt"; const legalFile = path.join(pack, legalName); await appendFile(legalFile, "\n"); const checksumsFile = path.join(pack, "SHA256SUMS"); const checksums = await readFile(checksumsFile, "utf8"); const updated = checksums.replace( new RegExp(`^[a-f0-9]{64} ${legalName}$`, "mu"), `${await digest(legalFile)} ${legalName}`, ); expect(updated).not.toBe(checksums); await writeFile(checksumsFile, updated); await expect(verifyPhpPack(pack)).rejects.toThrow( `Packed PHP legal asset ${legalName} SHA-256 mismatch`, ); }); });