Compare commits
4 Commits
v2026.05.1
...
999a1314a8
| Author | SHA1 | Date | |
|---|---|---|---|
| 999a1314a8 | |||
| 602f00262b | |||
| 475bab8bf6 | |||
| beb14bf834 |
@@ -8,27 +8,110 @@ set -eu
|
||||
|
||||
PUBLIC_FILE="/app/apps/web/.output/public/assets/file-D5WsIgJH.js"
|
||||
SSR_FILE="/app/apps/web/.output/server/_ssr/pdf-document-COfeOLVC.mjs"
|
||||
SERVER_INDEX_FILE="/app/apps/web/.output/server/index.mjs"
|
||||
PDF_FILENAME="ZhiboWang-Resume.pdf"
|
||||
CACHE_BUST="rr-filename-20260519"
|
||||
|
||||
cp "$PUBLIC_FILE" "$PUBLIC_FILE.bak-filename" 2>/dev/null || true
|
||||
cp "$SSR_FILE" "$SSR_FILE.bak-filename" 2>/dev/null || true
|
||||
cp "$SERVER_INDEX_FILE" "$SERVER_INDEX_FILE.bak-filename" 2>/dev/null || true
|
||||
|
||||
node - <<'NODE'
|
||||
const fs = require('fs');
|
||||
const crypto = require('crypto');
|
||||
|
||||
const publicFile = '/app/apps/web/.output/public/assets/file-D5WsIgJH.js';
|
||||
const ssrFile = '/app/apps/web/.output/server/_ssr/pdf-document-COfeOLVC.mjs';
|
||||
const serverIndexFile = '/app/apps/web/.output/server/index.mjs';
|
||||
const pdfFilename = 'ZhiboWang-Resume.pdf';
|
||||
const cacheBust = 'rr-filename-20260519';
|
||||
|
||||
function makeEtag(buffer) {
|
||||
const digest = crypto.createHash('sha1').update(buffer).digest('base64').replace(/=+$/g, '');
|
||||
return `"${buffer.length.toString(16)}-${digest}"`;
|
||||
}
|
||||
|
||||
function patchStaticManifestEntry(source, urlPath, filePath) {
|
||||
const buffer = fs.readFileSync(filePath);
|
||||
const startMarker = `"${urlPath}": {`;
|
||||
const start = source.indexOf(startMarker);
|
||||
if (start === -1) throw new Error(`Static manifest entry not found for ${urlPath}`);
|
||||
|
||||
const commaEnd = source.indexOf('\n\t},', start);
|
||||
const objectEnd = source.indexOf('\n\t}', start);
|
||||
const end = commaEnd === -1 ? objectEnd : Math.min(commaEnd, objectEnd);
|
||||
if (end === -1) throw new Error(`Static manifest entry end not found for ${urlPath}`);
|
||||
|
||||
let entry = source.slice(start, end);
|
||||
entry = entry
|
||||
.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, start) + entry + source.slice(end);
|
||||
}
|
||||
|
||||
function patchPublicImporters() {
|
||||
const assetsDir = '/app/apps/web/.output/public/assets';
|
||||
const files = fs
|
||||
.readdirSync(assetsDir)
|
||||
.filter((name) => name.endsWith('.js'))
|
||||
.map((name) => `${assetsDir}/${name}`)
|
||||
.filter((file) => fs.readFileSync(file, 'utf8').includes('file-D5WsIgJH.js'));
|
||||
|
||||
for (const file of files) {
|
||||
let source = fs.readFileSync(file, 'utf8');
|
||||
source = source.replace(/\.\/file-D5WsIgJH\.js(?:\?v=rr-filename-[A-Za-z0-9-]+)?/g, `./file-D5WsIgJH.js?v=${cacheBust}`);
|
||||
fs.writeFileSync(file, source);
|
||||
}
|
||||
|
||||
return files;
|
||||
}
|
||||
|
||||
let publicJs = fs.readFileSync(publicFile, 'utf8');
|
||||
publicJs = publicJs.replace(
|
||||
/function t\(t,n\)\{return`\$\{e\(t\)\}\$\{n\?`\.\$\{n\}`:""\}`\}/,
|
||||
'function t(e,t){let n=(e||"resume").toString().trim()||"resume";return`${n}${t?`.${t}`:""}`}'
|
||||
);
|
||||
const publicReplacement = `function t(e,t){if(t==="pdf")return"${pdfFilename}";let n=(e||"resume").toString().trim()||"resume";return\`\${n}\${t?\`.\${t}\`:""}\`}`;
|
||||
if (!publicJs.includes(publicReplacement)) {
|
||||
const start = publicJs.indexOf('function t(');
|
||||
const end = publicJs.indexOf('function n(', start);
|
||||
if (start === -1 || end === -1) throw new Error('Public generateFilename marker not found');
|
||||
publicJs = publicJs.slice(0, start) + publicReplacement + publicJs.slice(end);
|
||||
}
|
||||
fs.writeFileSync(publicFile, publicJs);
|
||||
|
||||
const ssrFile = '/app/apps/web/.output/server/_ssr/pdf-document-COfeOLVC.mjs';
|
||||
let ssr = fs.readFileSync(ssrFile, 'utf8');
|
||||
ssr = ssr.replace(
|
||||
/function generateFilename\(prefix, extension\) \{\n\s*return `\$\{slugify\(prefix\)\}\$\{extension \? `\.\$\{extension\}` : ""\}`;\n\}/,
|
||||
'function generateFilename(prefix, extension) {\n\tconst filename = (prefix || "resume").toString().trim() || "resume";\n\treturn `${filename}${extension ? `.${extension}` : ""}`;\n}'
|
||||
);
|
||||
const ssrReplacement = `function generateFilename(prefix, extension) {\n\tif (extension === "pdf") return "${pdfFilename}";\n\tconst filename = (prefix || "resume").toString().trim() || "resume";\n\treturn \`\${filename}\${extension ? \`.\${extension}\` : ""}\`;\n}`;
|
||||
if (!ssr.includes(ssrReplacement)) {
|
||||
const start = ssr.indexOf('function generateFilename(');
|
||||
const end = ssr.indexOf('\nfunction downloadWithAnchor(', start);
|
||||
if (start === -1 || end === -1) throw new Error('SSR generateFilename marker not found');
|
||||
ssr = ssr.slice(0, start) + ssrReplacement + ssr.slice(end);
|
||||
}
|
||||
fs.writeFileSync(ssrFile, ssr);
|
||||
|
||||
const importers = patchPublicImporters();
|
||||
let serverIndex = fs.readFileSync(serverIndexFile, 'utf8');
|
||||
serverIndex = patchStaticManifestEntry(serverIndex, '/assets/file-D5WsIgJH.js', publicFile);
|
||||
for (const file of importers) {
|
||||
const urlPath = `/assets/${file.split('/').pop()}`;
|
||||
serverIndex = patchStaticManifestEntry(serverIndex, urlPath, file);
|
||||
}
|
||||
fs.writeFileSync(serverIndexFile, serverIndex);
|
||||
NODE
|
||||
|
||||
node --check "$PUBLIC_FILE" >/dev/null
|
||||
node --check "$SSR_FILE" >/dev/null
|
||||
node --check "$SERVER_INDEX_FILE" >/dev/null
|
||||
SH
|
||||
|
||||
# Nitro loads the static asset manifest into memory at process startup. Restart
|
||||
# after patching so updated content-length/etag values are used immediately.
|
||||
docker restart "$CONTAINER" >/dev/null
|
||||
|
||||
for _ in $(seq 1 60); do
|
||||
health="$(docker inspect -f '{{if .State.Health}}{{.State.Health.Status}}{{else}}{{.State.Status}}{{end}}' "$CONTAINER")"
|
||||
[ "$health" = "healthy" ] && exit 0
|
||||
sleep 2
|
||||
done
|
||||
|
||||
docker logs --tail 80 "$CONTAINER" >&2
|
||||
exit 1
|
||||
|
||||
@@ -9,7 +9,7 @@ set -eu
|
||||
SSR_FILE="/app/apps/web/.output/server/_ssr/pdf-document-COfeOLVC.mjs"
|
||||
PUBLIC_FILE="/app/apps/web/.output/public/assets/pdf-document-BplbXx-0.js"
|
||||
SERVER_INDEX_FILE="/app/apps/web/.output/server/index.mjs"
|
||||
CACHE_BUST="rr-glalie-layout-20260518"
|
||||
CACHE_BUST="rr-glalie-layout-20260519"
|
||||
|
||||
test -f "$SSR_FILE.bak-glalie-layout" || cp "$SSR_FILE" "$SSR_FILE.bak-glalie-layout" 2>/dev/null || true
|
||||
test -f "$PUBLIC_FILE.bak-glalie-layout" || cp "$PUBLIC_FILE" "$PUBLIC_FILE.bak-glalie-layout" 2>/dev/null || true
|
||||
@@ -22,7 +22,8 @@ const crypto = require("crypto");
|
||||
const ssrFile = "/app/apps/web/.output/server/_ssr/pdf-document-COfeOLVC.mjs";
|
||||
const publicFile = "/app/apps/web/.output/public/assets/pdf-document-BplbXx-0.js";
|
||||
const serverIndexFile = "/app/apps/web/.output/server/index.mjs";
|
||||
const cacheBust = "rr-glalie-layout-20260518";
|
||||
const cacheBust = "rr-glalie-layout-20260519";
|
||||
const browserBufferPolyfill = "var Buffer=globalThis.Buffer??{isBuffer:()=>false,allocUnsafe:e=>new Uint8Array(e),alloc:e=>new Uint8Array(e)};/* rr-browser-buffer-polyfill */";
|
||||
|
||||
function replaceOnce(source, from, to, label) {
|
||||
if (source.includes(to)) return source;
|
||||
@@ -103,7 +104,20 @@ function patchSsr(source) {
|
||||
.replace(/metrics\.gapY\(2\.2\)/g, "metrics.gapY(3.0)");
|
||||
}
|
||||
|
||||
function ensureBrowserBufferPolyfill(source) {
|
||||
if (source.includes("rr-browser-buffer-polyfill")) return source;
|
||||
|
||||
const insertAt = source.indexOf(";") + 1;
|
||||
if (insertAt <= 0 || !source.startsWith("import")) {
|
||||
throw new Error("public PDF bundle import prelude not found");
|
||||
}
|
||||
|
||||
return source.slice(0, insertAt) + browserBufferPolyfill + source.slice(insertAt);
|
||||
}
|
||||
|
||||
function patchPublic(source) {
|
||||
source = ensureBrowserBufferPolyfill(source);
|
||||
|
||||
source = source
|
||||
.replace(/([A-Za-z_$][\w$]*)=([A-Za-z_$][\w$]*)\*\.(?:2|08);return\{paragraph:\{marginTop:\1,marginBottom:\1\},listItem:\{marginTop:\1,marginBottom:\1\}\}/,
|
||||
"$1=$2*.08;return{paragraph:{marginTop:$1,marginBottom:$1},listItem:{marginTop:$1,marginBottom:$1}}")
|
||||
@@ -158,7 +172,7 @@ function patchImporters() {
|
||||
|
||||
for (const file of files) {
|
||||
let source = fs.readFileSync(file, "utf8");
|
||||
source = source.replace(/\.\/pdf-document-BplbXx-0\.js(?:\?v=rr-glalie-layout-20260518)?/g, `./pdf-document-BplbXx-0.js?v=${cacheBust}`);
|
||||
source = source.replace(/\.\/pdf-document-BplbXx-0\.js(?:\?v=rr-[^"'`]+)?/g, `./pdf-document-BplbXx-0.js?v=${cacheBust}`);
|
||||
fs.writeFileSync(file, source);
|
||||
}
|
||||
|
||||
@@ -207,3 +221,16 @@ node --check "$SSR_FILE" >/dev/null
|
||||
node --check "$PUBLIC_FILE" >/dev/null
|
||||
node --check "$SERVER_INDEX_FILE" >/dev/null
|
||||
SH
|
||||
|
||||
# Nitro loads the static asset manifest into memory at process startup. Restart
|
||||
# after patching so updated content-length/etag values are used immediately.
|
||||
docker restart "$CONTAINER" >/dev/null
|
||||
|
||||
for _ in $(seq 1 60); do
|
||||
health="$(docker inspect -f '{{if .State.Health}}{{.State.Health.Status}}{{else}}{{.State.Status}}{{end}}' "$CONTAINER")"
|
||||
[ "$health" = "healthy" ] && exit 0
|
||||
sleep 2
|
||||
done
|
||||
|
||||
docker logs --tail 80 "$CONTAINER" >&2
|
||||
exit 1
|
||||
|
||||
Reference in New Issue
Block a user