161 lines
10 KiB
Bash
161 lines
10 KiB
Bash
#!/bin/sh
|
|
set -eu
|
|
|
|
SSR_FILE="/app/apps/web/.output/server/_ssr/pdf-document-COfeOLVC.mjs"
|
|
PUBLIC_FILENAME_FILE="/app/apps/web/.output/public/assets/file-D5WsIgJH.js"
|
|
PUBLIC_PDF_FILE="/app/apps/web/.output/public/assets/pdf-document-BplbXx-0.js"
|
|
SERVER_INDEX_FILE="/app/apps/web/.output/server/index.mjs"
|
|
CACHE_BUST_FILENAME="rr-filename-title-20260520"
|
|
CACHE_BUST_PDF="rr-glalie-layout-20260520"
|
|
|
|
node - <<'NODE'
|
|
const fs = require("fs");
|
|
const crypto = require("crypto");
|
|
|
|
const ssrFile = "/app/apps/web/.output/server/_ssr/pdf-document-COfeOLVC.mjs";
|
|
const publicFilenameFile = "/app/apps/web/.output/public/assets/file-D5WsIgJH.js";
|
|
const publicPdfFile = "/app/apps/web/.output/public/assets/pdf-document-BplbXx-0.js";
|
|
const serverIndexFile = "/app/apps/web/.output/server/index.mjs";
|
|
const filenameCacheBust = "rr-filename-title-20260520";
|
|
const pdfCacheBust = "rr-glalie-layout-20260520";
|
|
const browserBufferPolyfill = "var Buffer=globalThis.Buffer??{isBuffer:()=>false,allocUnsafe:e=>new Uint8Array(e),alloc:e=>new Uint8Array(e)};/* rr-browser-buffer-polyfill */";
|
|
|
|
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) {
|
|
if (!fs.existsSync(filePath)) return source;
|
|
const buffer = fs.readFileSync(filePath);
|
|
const startMarker = `"${urlPath}": {`;
|
|
const start = source.indexOf(startMarker);
|
|
if (start === -1) return source;
|
|
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) return source;
|
|
|
|
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 replaceOnce(source, from, to) {
|
|
return source.includes(to) ? source : source.replace(from, to);
|
|
}
|
|
|
|
function replaceRegexOnce(source, regex, to) {
|
|
return source.includes(to) ? source : source.replace(regex, to);
|
|
}
|
|
|
|
function patchFilenameBundle() {
|
|
let source = fs.readFileSync(publicFilenameFile, "utf8");
|
|
const replacement = 'function t(e,t){let n=(e||"resume").toString().trim()||"resume";return n=n.replace(/[\\\\/:*?"<>|]/g,"-").replace(/\\s+/g," ").replace(/\\.+$/,"").trim()||"resume",t&&n.toLowerCase().endsWith("."+t.toLowerCase())?n:`${n}${t?`.${t}`:""}`}';
|
|
if (!source.includes(replacement)) {
|
|
const start = source.indexOf("function t(");
|
|
const end = source.indexOf("function n(", start);
|
|
if (start === -1 || end === -1) throw new Error("filename function marker not found");
|
|
source = source.slice(0, start) + replacement + source.slice(end);
|
|
fs.writeFileSync(publicFilenameFile, source);
|
|
}
|
|
}
|
|
|
|
function patchSsr(source) {
|
|
source = source.replace(/\n\t\tname: "",\n\t\tdata: \{/, "\n\t\tname: resume.name,\n\t\tdata: {");
|
|
const filenameReplacement = `function generateFilename(prefix, extension) {\n\tlet filename = (prefix || "resume").toString().trim() || "resume";\n\tfilename = filename.replace(/[\\\\/:*?"<>|]/g, "-").replace(/\\s+/g, " ").replace(/\\.+$/, "").trim() || "resume";\n\treturn extension && filename.toLowerCase().endsWith(\`.\${extension.toLowerCase()}\`) ? filename : \`\${filename}\${extension ? \`.\${extension}\` : ""}\`;\n}`;
|
|
if (!source.includes(filenameReplacement)) {
|
|
const start = source.indexOf("function generateFilename(");
|
|
const end = source.indexOf("\nfunction downloadWithAnchor(", start);
|
|
if (start === -1 || end === -1) throw new Error("SSR generateFilename marker not found");
|
|
source = source.slice(0, start) + filenameReplacement + source.slice(end);
|
|
}
|
|
|
|
source = source
|
|
.replace(/const sideMargin = bodyLineHeight \* \.(?:2|08);/, "const sideMargin = bodyLineHeight * .08;")
|
|
.replace(/metrics\.gapY\(3\.5\)/g, "metrics.gapY(3.0)")
|
|
.replace(/metrics\.gapY\(2\.6\)/g, "metrics.gapY(3.0)")
|
|
.replace(/metrics\.gapY\(2\.2\)/g, "metrics.gapY(3.0)")
|
|
.replace(/style: composeStyles\(styles\.sidebarContent, \{ rowGap: metrics\.sectionGap \}\),/g, "style: composeStyles(styles.sidebarContent, { rowGap: metrics.gapY(3.0) }),")
|
|
.replace(/style: composeStyles\(styles\.mainContent, \{ rowGap: metrics\.sectionGap \}\),/g, "style: composeStyles(styles.mainContent, { rowGap: metrics.gapY(3.0) }),")
|
|
.replace(/sectionHeading: \{\s*borderBottomWidth: 1,\s*borderBottomColor: primary(?:,\s*paddingBottom: 1(?:\.3)?)?\s*\},/, "sectionHeading: {\n\t\t\t\t\tborderBottomWidth: 1,\n\t\t\t\t\tborderBottomColor: primary,\n\t\t\t\t\tpaddingBottom: 1.3\n\t\t\t\t},");
|
|
source = replaceRegexOnce(
|
|
source,
|
|
/sectionHeading: \{\s*borderBottomWidth: 1,\s*borderBottomColor: primary,\s*paddingBottom: 1(?:\.3)?\s*\},\s*item: \{ rowGap: metrics\.gapY\(\.125\) \},/,
|
|
"sectionHeading: {\n\t\t\t\t\tborderBottomWidth: 1,\n\t\t\t\t\tborderBottomColor: primary,\n\t\t\t\t\tpaddingBottom: 1.3\n\t\t\t\t},\n\t\t\t\tsectionItems: { paddingTop: metrics.gapY(.55) },\n\t\t\t\titem: { rowGap: metrics.gapY(.2) },",
|
|
);
|
|
source = replaceRegexOnce(
|
|
source,
|
|
/sidebarColumn: \{\s*zIndex: 1,\s*backgroundColor: primaryTint,\s*paddingHorizontal: metrics\.page\.paddingHorizontal,\s*paddingTop: metrics\.page\.paddingVertical,\s*(?:paddingBottom: metrics\.page\.paddingVertical,\s*)?rowGap: (?:metrics\.sectionGap|metrics\.gapY\([^)]+\))\s*\},/,
|
|
"sidebarColumn: {\n\t\t\t\t\tzIndex: 1,\n\t\t\t\t\tbackgroundColor: primaryTint,\n\t\t\t\t\tpaddingHorizontal: metrics.page.paddingHorizontal,\n\t\t\t\t\tpaddingTop: metrics.page.paddingVertical,\n\t\t\t\t\tpaddingBottom: metrics.page.paddingVertical,\n\t\t\t\t\trowGap: metrics.gapY(3.0)\n\t\t\t\t},",
|
|
);
|
|
source = replaceRegexOnce(
|
|
source,
|
|
/mainContent: \{\s*paddingHorizontal: metrics\.page\.paddingHorizontal,\s*paddingTop: metrics\.page\.paddingVertical,\s*(?:paddingBottom: metrics\.page\.paddingVertical\s*)?\},/,
|
|
"mainContent: {\n\t\t\t\t\tpaddingHorizontal: metrics.page.paddingHorizontal,\n\t\t\t\t\tpaddingTop: metrics.page.paddingVertical,\n\t\t\t\t\tpaddingBottom: metrics.page.paddingVertical\n\t\t\t\t},",
|
|
);
|
|
return source;
|
|
}
|
|
|
|
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("PDF bundle import prelude not found");
|
|
return source.slice(0, insertAt) + browserBufferPolyfill + source.slice(insertAt);
|
|
}
|
|
|
|
function patchPublicPdf(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}}")
|
|
.replace(/[oc]\.gapY\(3\.5\)/g, (m) => `${m[0]}.gapY(3.0)`)
|
|
.replace(/[oc]\.gapY\(2\.6\)/g, (m) => `${m[0]}.gapY(3.0)`)
|
|
.replace(/[oc]\.gapY\(2\.2\)/g, (m) => `${m[0]}.gapY(3.0)`)
|
|
.replace(/style:\$\(a\.sidebarContent,\{rowGap:o\.sectionGap\}\)/g, "style:$(a.sidebarContent,{rowGap:o.gapY(3.0)})")
|
|
.replace(/style:\$\(a\.mainContent,\{rowGap:o\.sectionGap\}\)/g, "style:$(a.mainContent,{rowGap:o.gapY(3.0)})")
|
|
.replace(/sectionHeading:\{borderBottomWidth:1,borderBottomColor:a(?:,paddingBottom:1(?:\.3)?)?\}/, "sectionHeading:{borderBottomWidth:1,borderBottomColor:a,paddingBottom:1.3}")
|
|
.replace(/sectionHeading:\{borderBottomWidth:1,borderBottomColor:a,paddingBottom:1(?:\.3)?\},item:\{rowGap:([a-zA-Z_$][\w$]*)\.gapY\(\.125\)\}/, "sectionHeading:{borderBottomWidth:1,borderBottomColor:a,paddingBottom:1.3},sectionItems:{paddingTop:$1.gapY(.55)},item:{rowGap:$1.gapY(.2)}");
|
|
source = replaceOnce(source, "sidebarColumn:{zIndex:1,backgroundColor:o,paddingHorizontal:c.page.paddingHorizontal,paddingTop:c.page.paddingVertical,rowGap:c.sectionGap}", "sidebarColumn:{zIndex:1,backgroundColor:o,paddingHorizontal:c.page.paddingHorizontal,paddingTop:c.page.paddingVertical,paddingBottom:c.page.paddingVertical,rowGap:c.gapY(3.0)}");
|
|
source = replaceOnce(source, "mainContent:{paddingHorizontal:c.page.paddingHorizontal,paddingTop:c.page.paddingVertical}", "mainContent:{paddingHorizontal:c.page.paddingHorizontal,paddingTop:c.page.paddingVertical,paddingBottom:c.page.paddingVertical}");
|
|
return source;
|
|
}
|
|
|
|
function patchImporters() {
|
|
const assetsDir = "/app/apps/web/.output/public/assets";
|
|
const files = fs.readdirSync(assetsDir).filter((name) => name.endsWith(".js")).map((name) => `${assetsDir}/${name}`);
|
|
const touched = [];
|
|
for (const file of files) {
|
|
let source = fs.readFileSync(file, "utf8");
|
|
let next = source
|
|
.replace(/\.\/file-D5WsIgJH\.js(?:\?v=rr-filename-[A-Za-z0-9-]+)?/g, `./file-D5WsIgJH.js?v=${filenameCacheBust}`)
|
|
.replace(/\.\/pdf-document-BplbXx-0\.js(?:\?v=rr-[^"'`]+)?/g, `./pdf-document-BplbXx-0.js?v=${pdfCacheBust}`);
|
|
if (next !== source) {
|
|
fs.writeFileSync(file, next);
|
|
touched.push(file);
|
|
}
|
|
}
|
|
return touched;
|
|
}
|
|
|
|
patchFilenameBundle();
|
|
fs.writeFileSync(ssrFile, patchSsr(fs.readFileSync(ssrFile, "utf8")));
|
|
fs.writeFileSync(publicPdfFile, patchPublicPdf(fs.readFileSync(publicPdfFile, "utf8")));
|
|
const importers = patchImporters();
|
|
|
|
let serverIndex = fs.readFileSync(serverIndexFile, "utf8");
|
|
serverIndex = patchStaticManifestEntry(serverIndex, "/assets/file-D5WsIgJH.js", publicFilenameFile);
|
|
serverIndex = patchStaticManifestEntry(serverIndex, "/assets/pdf-document-BplbXx-0.js", publicPdfFile);
|
|
for (const file of importers) {
|
|
serverIndex = patchStaticManifestEntry(serverIndex, `/assets/${file.split("/").pop()}`, file);
|
|
}
|
|
fs.writeFileSync(serverIndexFile, serverIndex);
|
|
NODE
|
|
|
|
node --check "$SSR_FILE" >/dev/null
|
|
node --check "$PUBLIC_FILENAME_FILE" >/dev/null
|
|
node --check "$PUBLIC_PDF_FILE" >/dev/null
|
|
node --check "$SERVER_INDEX_FILE" >/dev/null
|