From 93aa73ab5e6fdc4c1f0b7916c4f818465e92dcc5 Mon Sep 17 00:00:00 2001 From: admin <572701190@qq.com> Date: Wed, 20 May 2026 02:56:44 +0800 Subject: [PATCH] fix stale frontend cache in install packages --- dist/SHA256SUMS | 6 +- dist/SHA256SUMS-20260520 | 6 +- ...reactive-resume-clean-install-20260520.zip | Bin 5610 -> 5610 bytes ...active-resume-personal-direct-20260520.zip | Bin 12540585 -> 12541631 bytes ...tive-resume-personal-qnap-nas-20260520.zip | Bin 12539285 -> 12540331 bytes .../patches/reactive-resume-runtime-patch.sh | 128 ++++++++++++++++- .../patches/reactive-resume-runtime-patch.sh | 128 ++++++++++++++++- scripts/test-personal-install-packages.sh | 129 ++++++++++++++++++ 8 files changed, 389 insertions(+), 8 deletions(-) diff --git a/dist/SHA256SUMS b/dist/SHA256SUMS index d5f30c5..2e28306 100644 --- a/dist/SHA256SUMS +++ b/dist/SHA256SUMS @@ -1,3 +1,3 @@ -4310b841d283d76465e8f7c53d630b24ad10e75ae5b6039399db1adcded8702e reactive-resume-clean-install-20260520.zip -c2a24e404fe47bf4c21c01d22fa21af0c86323ef246606fe7152ad293b46ab34 reactive-resume-personal-direct-20260520.zip -5d26c24423159df199b9c2263d02de64912d7c4f44f7e17bf5beef2d2d601e8f reactive-resume-personal-qnap-nas-20260520.zip +1a1571bbed2c59c0003daa3c4c41cda7464b03cb8c4706bd7bf507e8abfaa7ea reactive-resume-clean-install-20260520.zip +34764a874e5477e439fa2860ef311e42264e90575c5a89e3f1651748708ef4df reactive-resume-personal-direct-20260520.zip +933541058567f73b6a0d9521863a0d592da6d0a546fd1c38587aeaa0f11cf61d reactive-resume-personal-qnap-nas-20260520.zip diff --git a/dist/SHA256SUMS-20260520 b/dist/SHA256SUMS-20260520 index d5f30c5..2e28306 100644 --- a/dist/SHA256SUMS-20260520 +++ b/dist/SHA256SUMS-20260520 @@ -1,3 +1,3 @@ -4310b841d283d76465e8f7c53d630b24ad10e75ae5b6039399db1adcded8702e reactive-resume-clean-install-20260520.zip -c2a24e404fe47bf4c21c01d22fa21af0c86323ef246606fe7152ad293b46ab34 reactive-resume-personal-direct-20260520.zip -5d26c24423159df199b9c2263d02de64912d7c4f44f7e17bf5beef2d2d601e8f reactive-resume-personal-qnap-nas-20260520.zip +1a1571bbed2c59c0003daa3c4c41cda7464b03cb8c4706bd7bf507e8abfaa7ea reactive-resume-clean-install-20260520.zip +34764a874e5477e439fa2860ef311e42264e90575c5a89e3f1651748708ef4df reactive-resume-personal-direct-20260520.zip +933541058567f73b6a0d9521863a0d592da6d0a546fd1c38587aeaa0f11cf61d reactive-resume-personal-qnap-nas-20260520.zip diff --git a/dist/reactive-resume-clean-install-20260520.zip b/dist/reactive-resume-clean-install-20260520.zip index 3d596760738fc2c1e65f5303f9dc826aba4956c3..78c815936db264677fc9e765dc86be5c815b2d08 100644 GIT binary patch delta 13 VcmaE*{Yra+>%`W8^8{50Gt39@DIQZ@BqBP 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 p<{b%KWa+8|Y^Dq^jtfaJB!g@IZz$TY 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 delta 4894 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 diff --git a/packages/reactive-resume-personal-direct/patches/reactive-resume-runtime-patch.sh b/packages/reactive-resume-personal-direct/patches/reactive-resume-runtime-patch.sh index 8975ef3..49b35de 100644 --- a/packages/reactive-resume-personal-direct/patches/reactive-resume-runtime-patch.sh +++ b/packages/reactive-resume-personal-direct/patches/reactive-resume-runtime-patch.sh @@ -62,7 +62,21 @@ const explicitSsrFile = process.env.SSR_FILE || ""; const serverIndexFile = process.env.SERVER_INDEX_FILE || path.join(outputDir, "server/index.mjs"); const filenameCacheBust = "rr-filename-title-20260520b"; const pdfCacheBust = "rr-glalie-layout-20260520"; +const appShellSuffix = "rr20260520c"; const browserBufferPolyfill = "var Buffer=globalThis.Buffer??{isBuffer:()=>false,allocUnsafe:e=>new Uint8Array(e),alloc:e=>new Uint8Array(e)};/* rr-browser-buffer-polyfill */"; +const serviceWorkerCleanup = `/* Reactive Resume personal deployment: disable stale PWA caches. */ +self.addEventListener("install", (event) => { + self.skipWaiting(); + event.waitUntil(caches.keys().then((keys) => Promise.all(keys.map((key) => caches.delete(key))))); +}); +self.addEventListener("activate", (event) => { + event.waitUntil((async () => { + await caches.keys().then((keys) => Promise.all(keys.map((key) => caches.delete(key)))); + await self.clients.claim(); + })()); +}); +self.addEventListener("fetch", () => {}); +`; function warn(message) { console.warn(`Reactive Resume runtime patch: ${message}`); @@ -111,6 +125,20 @@ function listJsFiles(dir) { .map((name) => path.join(dir, name)); } +function listFilesRecursive(dir, predicate) { + if (!fs.existsSync(dir)) return []; + const result = []; + for (const entry of fs.readdirSync(dir, { withFileTypes: true })) { + const file = path.join(dir, entry.name); + if (entry.isDirectory()) { + result.push(...listFilesRecursive(file, predicate)); + } else if (predicate(file)) { + result.push(file); + } + } + return result; +} + function replaceOnce(source, from, to) { return source.includes(to) ? source : source.replace(from, to); } @@ -185,6 +213,79 @@ function patchSsr(source) { return source; } +function patchAppManifest(file, oldBase, newBase) { + if (!fs.existsSync(file)) return false; + let source = read(file); + const next = source.replace(new RegExp(`/assets/${escapeRegex(oldBase)}(?:\\?v=rr-app-shell-[A-Za-z0-9-]+)?`, "g"), `/assets/${newBase}`); + if (next !== source) { + write(file, next); + return true; + } + return false; +} + +function cloneStaticManifestEntry(source, oldUrl, newUrl, oldBase, newBase, newFile) { + const marker = `\t"${oldUrl}": {`; + const start = source.indexOf(marker); + if (start === -1) { + warn(`static manifest entry not found for ${oldUrl}, skipped app shell cache bust`); + return source; + } + + if (source.includes(`\t"${newUrl}": {`)) { + return patchStaticManifestEntry(source, newUrl, newFile); + } + + const close = source.indexOf("\n\t}", start); + if (close === -1 || source[close + 3] !== ",") { + warn(`static manifest entry close marker not found for ${oldUrl}, skipped app shell cache bust`); + return source; + } + + let entry = source.slice(start, close + 3); + const buffer = fs.readFileSync(newFile); + entry = entry + .replace(oldUrl, newUrl) + .replace(`../public/assets/${oldBase}`, `../public/assets/${newBase}`) + .replace(/"etag": "(?:\\.|[^"\\])*"/, `"etag": ${JSON.stringify(makeEtag(buffer))}`) + .replace(/"mtime": "(?:\\.|[^"\\])*"/, `"mtime": ${JSON.stringify(new Date().toISOString())}`) + .replace(/"size": \d+/, `"size": ${buffer.length}`); + + return source.slice(0, close + 4) + "\n" + entry + "," + source.slice(close + 4); +} + +function patchAppShellEntry(assetFiles) { + const indexFile = assetFiles.find((file) => /^index-[A-Za-z0-9_-]+\.js$/.test(path.basename(file))); + if (!indexFile) { + warn("index app shell bundle not found, skipped app shell cache bust"); + return null; + } + + const oldBase = path.basename(indexFile); + const newBase = oldBase.replace(/\.js$/, `-${appShellSuffix}.js`); + const newFile = path.join(path.dirname(indexFile), newBase); + if (!fs.existsSync(newFile)) { + fs.copyFileSync(indexFile, newFile); + } + return { oldBase, newBase, newFile }; +} + +function patchAppShellImporters(assetFiles, oldBase, newBase) { + const touched = []; + for (const file of assetFiles) { + if (path.basename(file) === oldBase) continue; + let source = read(file); + let next = source + .replace(new RegExp(`\\./${escapeRegex(oldBase)}(?:\\?v=rr-app-shell-[A-Za-z0-9-]+)?`, "g"), `./${newBase}`) + .replace(new RegExp(`/assets/${escapeRegex(oldBase)}(?:\\?v=rr-app-shell-[A-Za-z0-9-]+)?`, "g"), `/assets/${newBase}`); + if (next !== source) { + write(file, next); + touched.push(file); + } + } + return touched; +} + function patchPublicPdf(source) { if (!source.includes("rr-browser-buffer-polyfill")) { const importPrelude = source.match(/^(?:import[^;]+;)+/); @@ -241,6 +342,16 @@ if (ssrFile) { warn("SSR bundle with generateFilename not found"); } +const appShell = patchAppShellEntry(assetFiles); +const appShellTouchedImporters = appShell ? patchAppShellImporters(assetFiles, appShell.oldBase, appShell.newBase) : []; +if (appShell) { + const appManifestFiles = [ + ...listFilesRecursive(path.join(outputDir, "server"), (file) => path.basename(file).startsWith("_tanstack-start-manifest") && file.endsWith(".mjs")), + ...listFilesRecursive(path.join(appDir, "apps/server/dist"), (file) => path.basename(file).startsWith("_tanstack-start-manifest") && file.endsWith(".mjs")), + ]; + for (const file of appManifestFiles) patchAppManifest(file, appShell.oldBase, appShell.newBase); +} + const pdfFile = assetFiles .filter((file) => path.basename(file).startsWith("pdf-document-")) .sort((a, b) => fs.statSync(b).size - fs.statSync(a).size)[0] || ""; @@ -270,9 +381,24 @@ for (const file of assetFiles) { if (fs.existsSync(serverIndexFile)) { let serverIndex = read(serverIndexFile); - for (const file of [...patchedFilenameFiles, pdfFile, ...touchedImporters].filter(Boolean)) { + for (const file of [...patchedFilenameFiles, pdfFile, ...touchedImporters, ...appShellTouchedImporters].filter(Boolean)) { serverIndex = patchStaticManifestEntry(serverIndex, `/assets/${path.basename(file)}`, file); } + if (appShell) { + serverIndex = cloneStaticManifestEntry( + serverIndex, + `/assets/${appShell.oldBase}`, + `/assets/${appShell.newBase}`, + appShell.oldBase, + appShell.newBase, + appShell.newFile, + ); + } + const serviceWorkerFile = path.join(path.dirname(assetsDir), "sw.js"); + if (fs.existsSync(serviceWorkerFile)) { + write(serviceWorkerFile, serviceWorkerCleanup); + serverIndex = patchStaticManifestEntry(serverIndex, "/sw.js", serviceWorkerFile); + } write(serverIndexFile, serverIndex); } diff --git a/packages/reactive-resume-personal-qnap-nas/patches/reactive-resume-runtime-patch.sh b/packages/reactive-resume-personal-qnap-nas/patches/reactive-resume-runtime-patch.sh index 8975ef3..49b35de 100644 --- a/packages/reactive-resume-personal-qnap-nas/patches/reactive-resume-runtime-patch.sh +++ b/packages/reactive-resume-personal-qnap-nas/patches/reactive-resume-runtime-patch.sh @@ -62,7 +62,21 @@ const explicitSsrFile = process.env.SSR_FILE || ""; const serverIndexFile = process.env.SERVER_INDEX_FILE || path.join(outputDir, "server/index.mjs"); const filenameCacheBust = "rr-filename-title-20260520b"; const pdfCacheBust = "rr-glalie-layout-20260520"; +const appShellSuffix = "rr20260520c"; const browserBufferPolyfill = "var Buffer=globalThis.Buffer??{isBuffer:()=>false,allocUnsafe:e=>new Uint8Array(e),alloc:e=>new Uint8Array(e)};/* rr-browser-buffer-polyfill */"; +const serviceWorkerCleanup = `/* Reactive Resume personal deployment: disable stale PWA caches. */ +self.addEventListener("install", (event) => { + self.skipWaiting(); + event.waitUntil(caches.keys().then((keys) => Promise.all(keys.map((key) => caches.delete(key))))); +}); +self.addEventListener("activate", (event) => { + event.waitUntil((async () => { + await caches.keys().then((keys) => Promise.all(keys.map((key) => caches.delete(key)))); + await self.clients.claim(); + })()); +}); +self.addEventListener("fetch", () => {}); +`; function warn(message) { console.warn(`Reactive Resume runtime patch: ${message}`); @@ -111,6 +125,20 @@ function listJsFiles(dir) { .map((name) => path.join(dir, name)); } +function listFilesRecursive(dir, predicate) { + if (!fs.existsSync(dir)) return []; + const result = []; + for (const entry of fs.readdirSync(dir, { withFileTypes: true })) { + const file = path.join(dir, entry.name); + if (entry.isDirectory()) { + result.push(...listFilesRecursive(file, predicate)); + } else if (predicate(file)) { + result.push(file); + } + } + return result; +} + function replaceOnce(source, from, to) { return source.includes(to) ? source : source.replace(from, to); } @@ -185,6 +213,79 @@ function patchSsr(source) { return source; } +function patchAppManifest(file, oldBase, newBase) { + if (!fs.existsSync(file)) return false; + let source = read(file); + const next = source.replace(new RegExp(`/assets/${escapeRegex(oldBase)}(?:\\?v=rr-app-shell-[A-Za-z0-9-]+)?`, "g"), `/assets/${newBase}`); + if (next !== source) { + write(file, next); + return true; + } + return false; +} + +function cloneStaticManifestEntry(source, oldUrl, newUrl, oldBase, newBase, newFile) { + const marker = `\t"${oldUrl}": {`; + const start = source.indexOf(marker); + if (start === -1) { + warn(`static manifest entry not found for ${oldUrl}, skipped app shell cache bust`); + return source; + } + + if (source.includes(`\t"${newUrl}": {`)) { + return patchStaticManifestEntry(source, newUrl, newFile); + } + + const close = source.indexOf("\n\t}", start); + if (close === -1 || source[close + 3] !== ",") { + warn(`static manifest entry close marker not found for ${oldUrl}, skipped app shell cache bust`); + return source; + } + + let entry = source.slice(start, close + 3); + const buffer = fs.readFileSync(newFile); + entry = entry + .replace(oldUrl, newUrl) + .replace(`../public/assets/${oldBase}`, `../public/assets/${newBase}`) + .replace(/"etag": "(?:\\.|[^"\\])*"/, `"etag": ${JSON.stringify(makeEtag(buffer))}`) + .replace(/"mtime": "(?:\\.|[^"\\])*"/, `"mtime": ${JSON.stringify(new Date().toISOString())}`) + .replace(/"size": \d+/, `"size": ${buffer.length}`); + + return source.slice(0, close + 4) + "\n" + entry + "," + source.slice(close + 4); +} + +function patchAppShellEntry(assetFiles) { + const indexFile = assetFiles.find((file) => /^index-[A-Za-z0-9_-]+\.js$/.test(path.basename(file))); + if (!indexFile) { + warn("index app shell bundle not found, skipped app shell cache bust"); + return null; + } + + const oldBase = path.basename(indexFile); + const newBase = oldBase.replace(/\.js$/, `-${appShellSuffix}.js`); + const newFile = path.join(path.dirname(indexFile), newBase); + if (!fs.existsSync(newFile)) { + fs.copyFileSync(indexFile, newFile); + } + return { oldBase, newBase, newFile }; +} + +function patchAppShellImporters(assetFiles, oldBase, newBase) { + const touched = []; + for (const file of assetFiles) { + if (path.basename(file) === oldBase) continue; + let source = read(file); + let next = source + .replace(new RegExp(`\\./${escapeRegex(oldBase)}(?:\\?v=rr-app-shell-[A-Za-z0-9-]+)?`, "g"), `./${newBase}`) + .replace(new RegExp(`/assets/${escapeRegex(oldBase)}(?:\\?v=rr-app-shell-[A-Za-z0-9-]+)?`, "g"), `/assets/${newBase}`); + if (next !== source) { + write(file, next); + touched.push(file); + } + } + return touched; +} + function patchPublicPdf(source) { if (!source.includes("rr-browser-buffer-polyfill")) { const importPrelude = source.match(/^(?:import[^;]+;)+/); @@ -241,6 +342,16 @@ if (ssrFile) { warn("SSR bundle with generateFilename not found"); } +const appShell = patchAppShellEntry(assetFiles); +const appShellTouchedImporters = appShell ? patchAppShellImporters(assetFiles, appShell.oldBase, appShell.newBase) : []; +if (appShell) { + const appManifestFiles = [ + ...listFilesRecursive(path.join(outputDir, "server"), (file) => path.basename(file).startsWith("_tanstack-start-manifest") && file.endsWith(".mjs")), + ...listFilesRecursive(path.join(appDir, "apps/server/dist"), (file) => path.basename(file).startsWith("_tanstack-start-manifest") && file.endsWith(".mjs")), + ]; + for (const file of appManifestFiles) patchAppManifest(file, appShell.oldBase, appShell.newBase); +} + const pdfFile = assetFiles .filter((file) => path.basename(file).startsWith("pdf-document-")) .sort((a, b) => fs.statSync(b).size - fs.statSync(a).size)[0] || ""; @@ -270,9 +381,24 @@ for (const file of assetFiles) { if (fs.existsSync(serverIndexFile)) { let serverIndex = read(serverIndexFile); - for (const file of [...patchedFilenameFiles, pdfFile, ...touchedImporters].filter(Boolean)) { + for (const file of [...patchedFilenameFiles, pdfFile, ...touchedImporters, ...appShellTouchedImporters].filter(Boolean)) { serverIndex = patchStaticManifestEntry(serverIndex, `/assets/${path.basename(file)}`, file); } + if (appShell) { + serverIndex = cloneStaticManifestEntry( + serverIndex, + `/assets/${appShell.oldBase}`, + `/assets/${appShell.newBase}`, + appShell.oldBase, + appShell.newBase, + appShell.newFile, + ); + } + const serviceWorkerFile = path.join(path.dirname(assetsDir), "sw.js"); + if (fs.existsSync(serviceWorkerFile)) { + write(serviceWorkerFile, serviceWorkerCleanup); + serverIndex = patchStaticManifestEntry(serverIndex, "/sw.js", serviceWorkerFile); + } write(serverIndexFile, serverIndex); } diff --git a/scripts/test-personal-install-packages.sh b/scripts/test-personal-install-packages.sh index 04861d7..4253980 100755 --- a/scripts/test-personal-install-packages.sh +++ b/scripts/test-personal-install-packages.sh @@ -111,6 +111,130 @@ if docker exec "$PROJECT-reactive-resume-1" sh -lc 'APP_DIR=$(cat /tmp/reactive- fi docker exec "$PROJECT-reactive-resume-1" sh -lc 'APP_DIR=$(cat /tmp/reactive-resume-app-dir); grep -R "String(t).trim().replace" "$APP_DIR/.output/public/assets"/file-*.js >/dev/null 2>&1' \ || fail "direct 包未在 file-*.js 下载工具中应用文件名补丁" +curl -fsS "http://127.0.0.1:3004/" >/tmp/reactive-resume-home.html \ + || fail "direct 包首页无法访问" +grep -E '/assets/index-[A-Za-z0-9_-]+-rr[0-9a-z]+\.js' /tmp/reactive-resume-home.html >/dev/null \ + || fail "direct 包首页未改用防缓存的 index 主入口文件名" +curl -fsS "http://127.0.0.1:3004/sw.js" >/tmp/reactive-resume-sw.js \ + || fail "direct 包 sw.js 无法访问" +grep -q 'disable stale PWA caches' /tmp/reactive-resume-sw.js \ + || fail "direct 包 sw.js 未替换为清理旧缓存版本" + +if command -v google-chrome >/dev/null 2>&1 && command -v node >/dev/null 2>&1; then + log "使用 Chrome 执行 direct 包首页 JS" + node <<'NODE' +const { spawn } = require("child_process"); +const fs = require("fs"); +const http = require("http"); + +const port = 9444; +const userData = "/tmp/reactive-resume-chrome-smoke"; +fs.rmSync(userData, { recursive: true, force: true }); + +const chrome = spawn("google-chrome", [ + "--headless=new", + "--disable-gpu", + "--no-sandbox", + `--remote-debugging-port=${port}`, + `--user-data-dir=${userData}`, + "about:blank", +], { stdio: ["ignore", "ignore", "ignore"] }); + +function request(path, method = "GET") { + return new Promise((resolve, reject) => { + const req = http.request({ host: "127.0.0.1", port, path, method }, (res) => { + let body = ""; + res.on("data", (chunk) => body += chunk); + res.on("end", () => resolve({ status: res.statusCode, body })); + }); + req.on("error", reject); + req.end(); + }); +} + +async function waitForChrome() { + for (let i = 0; i < 60; i++) { + try { + const res = await request("/json/version"); + if (res.status === 200) return; + } catch {} + await new Promise((resolve) => setTimeout(resolve, 100)); + } + throw new Error("Chrome remote debugging was not ready"); +} + +(async () => { + await waitForChrome(); + const tab = JSON.parse((await request("/json/new?about:blank", "PUT")).body); + const ws = new WebSocket(tab.webSocketDebuggerUrl); + let id = 0; + const pending = new Map(); + const errors = []; + const indexRequests = []; + + ws.onmessage = (event) => { + const message = JSON.parse(event.data); + if (message.id && pending.has(message.id)) { + pending.get(message.id)(message); + pending.delete(message.id); + return; + } + if (message.method === "Runtime.exceptionThrown") { + errors.push(message.params.exceptionDetails); + } + if (message.method === "Network.requestWillBeSent" && message.params.request.url.includes("index-")) { + indexRequests.push(message.params.request.url); + } + }; + await new Promise((resolve) => { ws.onopen = resolve; }); + + function send(method, params = {}) { + return new Promise((resolve) => { + pending.set(++id, resolve); + ws.send(JSON.stringify({ id, method, params })); + }); + } + + await send("Runtime.enable"); + await send("Page.enable"); + await send("Network.enable"); + await send("Page.navigate", { url: "http://127.0.0.1:3004/" }); + await new Promise((resolve) => setTimeout(resolve, 5000)); + + const result = await send("Runtime.evaluate", { + expression: "document.body.innerText.includes('A free and open-source resume builder')", + returnByValue: true, + }); + ws.close(); + chrome.kill("SIGKILL"); + + if (errors.length > 0) { + console.error(JSON.stringify(errors.map((error) => ({ + text: error.text, + url: error.url, + line: error.lineNumber, + column: error.columnNumber, + exception: error.exception?.description, + })), null, 2)); + process.exit(1); + } + if (!result.result?.result?.value) { + console.error("Chrome did not render the expected home page text"); + process.exit(1); + } + if (indexRequests.some((url) => /\/assets\/index-[A-Za-z0-9_-]+\.js$/.test(url) && !/-rr[0-9a-z]+\.js$/.test(url))) { + console.error(`Chrome still requested stale app shell: ${indexRequests.join(", ")}`); + process.exit(1); + } +})().catch((error) => { + chrome.kill("SIGKILL"); + console.error(error); + process.exit(1); +}); +NODE +else + log "未找到 google-chrome,跳过浏览器级首页 JS 测试" +fi log "离线检查 arm64/QNAP 镜像布局" ARM64_DIGEST="$( @@ -173,6 +297,11 @@ grep -R -F 'replace(/[\\/:*?"<>|]/g' "$ARM64_ASSETS_DIR" >/dev/null \ if grep -R -E "index-[A-Za-z0-9_-]+\\.js\\?v=rr-filename-title" "$ARM64_ASSETS_DIR" >/dev/null 2>&1; then fail "arm64 补丁错误地给 index 主入口追加了 rr-filename-title 缓存标记" fi +find "$TMP_DIR/arm64-root/app" -type f -exec grep -qE 'index-[A-Za-z0-9_-]+-rr[0-9a-z]+\.js' {} \; -print -quit \ + | grep -q . \ + || fail "arm64 补丁未改用防缓存的 index 主入口文件名" +find "$TMP_DIR/arm64-root/app" -path '*/sw.js' -type f -print0 | xargs -0 grep -l 'disable stale PWA caches' >/dev/null \ + || fail "arm64 sw.js 未替换为清理旧缓存版本" grep -q 'function generateFilename(prefix, extension)' "$ARM64_FILENAME_ENTRY" \ || fail "arm64 server entry 未包含 generateFilename" grep -F 'filename.replace(/[\\/:*?"<>|]/g' "$ARM64_FILENAME_ENTRY" >/dev/null \