fix stale frontend cache in install packages
This commit is contained in:
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user