A manifest and icons rendered from icon.svg make the app installable. The native build emits a service worker that precaches exactly the built files and passes everything else to the network, so ROMs never reach its cache. New versions wait for the player's Update, which saves before reloading. Savegames offers installation, iPhone steps and persistent storage, and a full disk gets its own save error. Acceptance on physical iPhone and Android devices remains. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
62 lines
2.4 KiB
JavaScript
62 lines
2.4 KiB
JavaScript
import { readdirSync, readFileSync, statSync } from "node:fs";
|
|
import { join } from "node:path";
|
|
import { listFiles } from "./service-worker.mjs";
|
|
|
|
const ICONS = new Set([
|
|
"icon-192.png",
|
|
"icon-512.png",
|
|
"icon-maskable-512.png",
|
|
"apple-touch-icon.png",
|
|
]);
|
|
let files = 0,
|
|
size = 0;
|
|
function inspect(directory) {
|
|
for (const name of readdirSync(directory)) {
|
|
const path = join(directory, name);
|
|
if (statSync(path).isDirectory()) {
|
|
inspect(path);
|
|
continue;
|
|
}
|
|
// The app icons are the only images: rendered from icon.svg, small.
|
|
if (ICONS.has(name)) {
|
|
if (statSync(path).size > 32_000)
|
|
throw new Error(`App icon is unexpectedly large: ${path}`);
|
|
files++;
|
|
size += statSync(path).size;
|
|
continue;
|
|
}
|
|
if (!/\.(html|js|css|svg|webmanifest)$/.test(name))
|
|
throw new Error(`Unexpected production asset: ${path}`);
|
|
if (/reference|vendor|binjgb/i.test(path))
|
|
throw new Error(`Reference tool in production: ${path}`);
|
|
const bytes = readFileSync(path),
|
|
text = bytes.toString("utf8");
|
|
if (/Binjgb|_emulator_|binjgb\.wasm|\.reference\/pokecrystal/.test(text))
|
|
throw new Error(`Reference dependency in production: ${path}`);
|
|
if (/data:(image|audio|application\/octet-stream)[^,]*;base64,/i.test(text))
|
|
throw new Error(`Embedded binary asset in production: ${path}`);
|
|
files++;
|
|
size += bytes.length;
|
|
}
|
|
}
|
|
inspect("dist");
|
|
|
|
// The service worker precaches exactly the shell: every built file, and
|
|
// nothing that could hold game data.
|
|
const worker = readFileSync("dist/sw.js", "utf8");
|
|
const shell = JSON.parse(/const SHELL = (\[.*\]);/.exec(worker)?.[1] ?? "null");
|
|
if (!Array.isArray(shell)) throw new Error("dist/sw.js has no app shell list.");
|
|
const expected = [
|
|
"./",
|
|
...listFiles("dist")
|
|
.filter((file) => file !== "sw.js" && file !== "index.html")
|
|
.map((file) => `./${file}`),
|
|
].sort();
|
|
if (JSON.stringify([...shell].sort()) !== JSON.stringify(expected))
|
|
throw new Error("The service worker's shell does not match the build.");
|
|
if (shell.some((file) => /\.(gbc?|rom|sav)$|crystal-save/i.test(file)))
|
|
throw new Error("The service worker would cache game data.");
|
|
console.log(
|
|
`Native release verified: ${files} files (HTML, JS, CSS, SVG, manifest, service worker and ${ICONS.size} icons), ${size} bytes; no emulator, ROM, extracted binary assets or reference checkout.`,
|
|
);
|