#!/bin/sh set -eu CONTAINER="${1:-reactive-resume-reactive-resume-1}" docker exec -u root -i "$CONTAINER" sh <<'SH' 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-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 test -f "$SERVER_INDEX_FILE.bak-glalie-layout" || cp "$SERVER_INDEX_FILE" "$SERVER_INDEX_FILE.bak-glalie-layout" 2>/dev/null || true node - <<'NODE' const fs = require("fs"); 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-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; if (!source.includes(from)) throw new Error(`Patch marker not found: ${label}`); return source.replace(from, to); } function replaceRegexOnce(source, regex, to, label) { if (source.includes(to)) return source; const next = source.replace(regex, to); if (next === source) throw new Error(`Patch marker not found: ${label}`); return next; } function patchSsr(source) { 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)"); source = source .replace( /style: composeStyles\(styles\.sidebarContent, \{ rowGap: metrics\.sectionGap \}\),/g, "style: composeStyles(styles.sidebarContent, { rowGap: metrics.gapY(2.2) }),", ) .replace( /style: composeStyles\(styles\.mainContent, \{ rowGap: metrics\.sectionGap \}\),/g, "style: composeStyles(styles.mainContent, { rowGap: metrics.gapY(2.2) }),", ); source = source.replace( /sectionHeading: \{\s*borderBottomWidth: 1,\s*borderBottomColor: primary(?:,\s*paddingBottom: 1(?:\.3)?)?\s*\},/, `sectionHeading: { \t\t\t\t\tborderBottomWidth: 1, \t\t\t\t\tborderBottomColor: primary, \t\t\t\t\tpaddingBottom: 1.3 \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: { \t\t\t\t\tborderBottomWidth: 1, \t\t\t\t\tborderBottomColor: primary, \t\t\t\t\tpaddingBottom: 1.3 \t\t\t\t}, \t\t\t\tsectionItems: { paddingTop: metrics.gapY(.55) }, \t\t\t\titem: { rowGap: metrics.gapY(.2) },`, "SSR Glalie section item spacing", ); 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: { \t\t\t\t\tzIndex: 1, \t\t\t\t\tbackgroundColor: primaryTint, \t\t\t\t\tpaddingHorizontal: metrics.page.paddingHorizontal, \t\t\t\t\tpaddingTop: metrics.page.paddingVertical, \t\t\t\t\tpaddingBottom: metrics.page.paddingVertical, \t\t\t\t\trowGap: metrics.gapY(3.0) \t\t\t\t},`, "SSR Glalie sidebar bottom padding", ); source = replaceRegexOnce( source, /mainContent: \{\s*paddingHorizontal: metrics\.page\.paddingHorizontal,\s*paddingTop: metrics\.page\.paddingVertical,\s*(?:paddingBottom: metrics\.page\.paddingVertical\s*)?\},/, `mainContent: { \t\t\t\t\tpaddingHorizontal: metrics.page.paddingHorizontal, \t\t\t\t\tpaddingTop: metrics.page.paddingVertical, \t\t\t\t\tpaddingBottom: metrics.page.paddingVertical \t\t\t\t},`, "SSR Glalie main bottom padding", ); return 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)"); } 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}}") .replace(/o\.gapY\(3\.5\)/g, "o.gapY(3.0)") .replace(/c\.gapY\(3\.5\)/g, "c.gapY(3.0)") .replace(/o\.gapY\(2\.6\)/g, "o.gapY(3.0)") .replace(/c\.gapY\(2\.6\)/g, "c.gapY(3.0)") .replace(/o\.gapY\(2\.2\)/g, "o.gapY(3.0)") .replace(/c\.gapY\(2\.2\)/g, "c.gapY(3.0)"); source = source .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)})"); source = source.replace( /sectionHeading:\{borderBottomWidth:1,borderBottomColor:a(?:,paddingBottom:1(?:\.3)?)?\}/, "sectionHeading:{borderBottomWidth:1,borderBottomColor:a,paddingBottom:1.3}", ); source = source.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)}", "public Glalie sidebar bottom padding", ); 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}", "public Glalie main bottom padding", ); return 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(/o\.gapY\(3\.5\)/g, "o.gapY(3.0)") .replace(/c\.gapY\(3\.5\)/g, "c.gapY(3.0)") .replace(/o\.gapY\(2\.6\)/g, "o.gapY(3.0)") .replace(/c\.gapY\(2\.6\)/g, "c.gapY(3.0)") .replace(/o\.gapY\(2\.2\)/g, "o.gapY(3.0)") .replace(/c\.gapY\(2\.2\)/g, "c.gapY(3.0)"); } function patchImporters() { 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("pdf-document-BplbXx-0.js")); for (const file of files) { let source = fs.readFileSync(file, "utf8"); source = source.replace(/\.\/pdf-document-BplbXx-0\.js(?:\?v=rr-[^"'`]+)?/g, `./pdf-document-BplbXx-0.js?v=${cacheBust}`); fs.writeFileSync(file, source); } return files; } 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); } fs.writeFileSync(ssrFile, patchSsr(fs.readFileSync(ssrFile, "utf8"))); fs.writeFileSync(publicFile, patchPublic(fs.readFileSync(publicFile, "utf8"))); const importers = patchImporters(); let serverIndex = fs.readFileSync(serverIndexFile, "utf8"); serverIndex = patchStaticManifestEntry(serverIndex, "/assets/pdf-document-BplbXx-0.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 "$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