fix install package runtime path detection
This commit is contained in:
@@ -18,9 +18,9 @@
|
|||||||
|
|
||||||
## 安装包
|
## 安装包
|
||||||
|
|
||||||
- `reactive-resume-clean-install-20260519`: 纯净迁移模板,域名、端口、FRP、密钥均为待填写占位。
|
- `reactive-resume-clean-install-20260520`: 纯净迁移模板,域名、端口、FRP、密钥均为待填写占位。
|
||||||
- `reactive-resume-personal-direct-20260519`: 服务器直接运行版,预置 `https://isiseg.huijutec.cn`、FRP `remotePort = 10004`,并内置当前简历与上传图片。
|
- `reactive-resume-personal-direct-20260520`: 服务器直接运行版,预置 `https://me.huijutec.cn`、FRP `remotePort = 10003`,并内置当前简历与上传图片。
|
||||||
- `reactive-resume-personal-qnap-nas-20260519`: 威联通 QNAP NAS 直接部署版,预置 `/share/Container/Reactive_Resume_Personal`、本地端口 `3004`、FRP `10004`,并内置当前简历与上传图片。
|
- `reactive-resume-personal-qnap-nas-20260520`: 威联通 QNAP NAS 直接部署版,预置 `/share/Container/reactive_resume`、本地端口 `3003`、FRP `10003`,并内置当前简历与上传图片。
|
||||||
|
|
||||||
## 启动
|
## 启动
|
||||||
|
|
||||||
|
|||||||
9
dist/SHA256SUMS
vendored
9
dist/SHA256SUMS
vendored
@@ -1,6 +1,3 @@
|
|||||||
f9daa11eeb735e1920d822094a7caf3f7eebcccf8467755bb6f904c68a53bdbf reactive-resume-clean-install-20260519.tar.gz
|
fa3b2b64a9afd7af60f57cfda8431af4e171cc1cdba4a6a2b89d50000a574f54 reactive-resume-clean-install-20260520.zip
|
||||||
18b247b33feaf9ccc4ce7516fb76ea3523c578e3099c77e7549bdfa2c68fd658 reactive-resume-personal-direct-20260519.tar.gz
|
d76b4920388fac71edcc6d84e1db609f62c4c2b7a3aa34f5fe3bad33813d9cba reactive-resume-personal-direct-20260520.zip
|
||||||
d2718004a8a0592f38096f426ef307c9df99b2f090dac733fb6c0f412d2e4916 reactive-resume-personal-qnap-nas-20260519.tar.gz
|
c949c68c6c3343c3d6db6ee13deb49c0a575ee9c758fac75ff647c6ff93e8da3 reactive-resume-personal-qnap-nas-20260520.zip
|
||||||
54bf5114e8ca9f29ae1feb0510f738396f354174f691bf96e863b41058742a58 reactive-resume-clean-install-20260519.zip
|
|
||||||
b7cdb405f24ff7653aac9d21f3bd347884e377c8a993d8bb1b7d9d77295425d2 reactive-resume-personal-direct-20260519.zip
|
|
||||||
d847a76e3cac28f9e973e21ba7bcbac89ccc940345694b3a3483cde9eed01ee0 reactive-resume-personal-qnap-nas-20260519.zip
|
|
||||||
|
|||||||
4
dist/SHA256SUMS-20260520
vendored
4
dist/SHA256SUMS-20260520
vendored
@@ -1,3 +1,3 @@
|
|||||||
fa3b2b64a9afd7af60f57cfda8431af4e171cc1cdba4a6a2b89d50000a574f54 reactive-resume-clean-install-20260520.zip
|
fa3b2b64a9afd7af60f57cfda8431af4e171cc1cdba4a6a2b89d50000a574f54 reactive-resume-clean-install-20260520.zip
|
||||||
4e95c039777ae2af6a216528ebd97911f59aa8a80c7816acde7ec8424eb6e59d reactive-resume-personal-direct-20260520.zip
|
d76b4920388fac71edcc6d84e1db609f62c4c2b7a3aa34f5fe3bad33813d9cba reactive-resume-personal-direct-20260520.zip
|
||||||
559c9c14dd939f1ca0406f9089060ade9d20d3279d8fac96b6d966b5a5185c37 reactive-resume-personal-qnap-nas-20260520.zip
|
c949c68c6c3343c3d6db6ee13deb49c0a575ee9c758fac75ff647c6ff93e8da3 reactive-resume-personal-qnap-nas-20260520.zip
|
||||||
|
|||||||
BIN
dist/reactive-resume-personal-direct-20260520.zip
vendored
BIN
dist/reactive-resume-personal-direct-20260520.zip
vendored
Binary file not shown.
BIN
dist/reactive-resume-personal-qnap-nas-20260520.zip
vendored
BIN
dist/reactive-resume-personal-qnap-nas-20260520.zip
vendored
Binary file not shown.
@@ -1,5 +1,5 @@
|
|||||||
TZ=Asia/Shanghai
|
TZ=Asia/Shanghai
|
||||||
APP_URL=https://isiseg.huijutec.cn
|
APP_URL=https://me.huijutec.cn
|
||||||
|
|
||||||
# Local debug access only: http://127.0.0.1:3004
|
# Local debug access only: http://127.0.0.1:3004
|
||||||
LOCAL_BIND_IP=127.0.0.1
|
LOCAL_BIND_IP=127.0.0.1
|
||||||
@@ -33,7 +33,7 @@ SMTP_HOST=
|
|||||||
SMTP_PORT=587
|
SMTP_PORT=587
|
||||||
SMTP_USER=
|
SMTP_USER=
|
||||||
SMTP_PASS=
|
SMTP_PASS=
|
||||||
SMTP_FROM=Reactive Resume <noreply@isiseg.huijutec.cn>
|
SMTP_FROM=Reactive Resume <noreply@me.huijutec.cn>
|
||||||
SMTP_SECURE=false
|
SMTP_SECURE=false
|
||||||
S3_ACCESS_KEY_ID=
|
S3_ACCESS_KEY_ID=
|
||||||
S3_SECRET_ACCESS_KEY=
|
S3_SECRET_ACCESS_KEY=
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
# Reactive Resume 个人简历直接运行安装包
|
# Reactive Resume 个人简历直接运行安装包
|
||||||
|
|
||||||
这套包已经按 `https://isiseg.huijutec.cn` 和 FRP `remotePort = 10004` 预置,可以在当前服务器上直接运行。包内已包含当前简历初始化数据、头像和作品集图片,首次启动后可直接访问公开简历。
|
这套包已经按 `https://me.huijutec.cn` 和 FRP `remotePort = 10003` 预置,可以在当前服务器上直接运行。包内已包含当前简历初始化数据、头像和作品集图片,首次启动后可直接访问公开简历。
|
||||||
|
|
||||||
## 启动
|
## 启动
|
||||||
|
|
||||||
@@ -11,18 +11,18 @@ docker compose -f compose.yml up -d
|
|||||||
启动后:
|
启动后:
|
||||||
|
|
||||||
- 本机调试地址:`http://127.0.0.1:3004`
|
- 本机调试地址:`http://127.0.0.1:3004`
|
||||||
- 公网访问地址:`https://isiseg.huijutec.cn`
|
- 公网访问地址:`https://me.huijutec.cn`
|
||||||
- 当前公开简历:`https://isiseg.huijutec.cn/audience/resume`
|
- 当前公开简历:`https://me.huijutec.cn/audience/resume`
|
||||||
- FRP 映射:本地 `reactive-resume:3000` -> 公网服务器 `10004`
|
- FRP 映射:本地 `reactive-resume:3000` -> 公网服务器 `10003`
|
||||||
|
|
||||||
## 反向代理要求
|
## 反向代理要求
|
||||||
|
|
||||||
公网服务器上的 Nginx Proxy Manager / 反向代理应配置:
|
公网服务器上的 Nginx Proxy Manager / 反向代理应配置:
|
||||||
|
|
||||||
- Domain Names:`isiseg.huijutec.cn`
|
- Domain Names:`me.huijutec.cn`
|
||||||
- Scheme:`http`
|
- Scheme:`http`
|
||||||
- Forward Hostname / IP:`82.157.255.195`
|
- Forward Hostname / IP:`82.157.255.195`
|
||||||
- Forward Port:`10004`
|
- Forward Port:`10003`
|
||||||
- Websockets Support:开启
|
- Websockets Support:开启
|
||||||
- SSL:按现有 huijutec.cn 域名策略配置
|
- SSL:按现有 huijutec.cn 域名策略配置
|
||||||
|
|
||||||
|
|||||||
@@ -25,6 +25,11 @@ services:
|
|||||||
command:
|
command:
|
||||||
- |
|
- |
|
||||||
sh /opt/reactive-resume-patches/reactive-resume-runtime-patch.sh
|
sh /opt/reactive-resume-patches/reactive-resume-runtime-patch.sh
|
||||||
|
APP_DIR="$(cat /tmp/reactive-resume-app-dir 2>/dev/null || true)"
|
||||||
|
if [ -z "$$APP_DIR" ]; then
|
||||||
|
APP_DIR="$(find /app -path '*/.output/server/index.mjs' -type f 2>/dev/null | head -n 1 | sed 's#/.output/server/index.mjs##')"
|
||||||
|
fi
|
||||||
|
cd "$${APP_DIR:-/app/apps/web}"
|
||||||
exec node .output/server/index.mjs
|
exec node .output/server/index.mjs
|
||||||
env_file:
|
env_file:
|
||||||
- .env
|
- .env
|
||||||
|
|||||||
@@ -14,4 +14,4 @@ proxies:
|
|||||||
type: "tcp"
|
type: "tcp"
|
||||||
localIP: "reactive-resume"
|
localIP: "reactive-resume"
|
||||||
localPort: 3000
|
localPort: 3000
|
||||||
remotePort: 10004
|
remotePort: 10003
|
||||||
|
|||||||
@@ -1,25 +1,57 @@
|
|||||||
#!/bin/sh
|
#!/bin/sh
|
||||||
set -eu
|
set -eu
|
||||||
|
|
||||||
SSR_FILE="/app/apps/web/.output/server/_ssr/pdf-document-COfeOLVC.mjs"
|
APP_DIR="${REACTIVE_RESUME_APP_DIR:-}"
|
||||||
PUBLIC_FILENAME_FILE="/app/apps/web/.output/public/assets/file-D5WsIgJH.js"
|
if [ -z "$APP_DIR" ]; then
|
||||||
PUBLIC_PDF_FILE="/app/apps/web/.output/public/assets/pdf-document-BplbXx-0.js"
|
for candidate in /app/apps/web /app; do
|
||||||
SERVER_INDEX_FILE="/app/apps/web/.output/server/index.mjs"
|
if [ -f "$candidate/.output/server/index.mjs" ]; then
|
||||||
CACHE_BUST_FILENAME="rr-filename-title-20260520"
|
APP_DIR="$candidate"
|
||||||
CACHE_BUST_PDF="rr-glalie-layout-20260520"
|
break
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -z "$APP_DIR" ]; then
|
||||||
|
index_file="$(find /app -path "*/.output/server/index.mjs" -type f 2>/dev/null | head -n 1 || true)"
|
||||||
|
if [ -n "$index_file" ]; then
|
||||||
|
APP_DIR="${index_file%/.output/server/index.mjs}"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -z "$APP_DIR" ] || [ ! -f "$APP_DIR/.output/server/index.mjs" ]; then
|
||||||
|
echo "Reactive Resume runtime patch skipped: .output/server/index.mjs not found under /app" >&2
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
printf "%s" "$APP_DIR" > /tmp/reactive-resume-app-dir
|
||||||
|
export APP_DIR
|
||||||
|
|
||||||
node - <<'NODE'
|
node - <<'NODE'
|
||||||
const fs = require("fs");
|
const fs = require("fs");
|
||||||
|
const path = require("path");
|
||||||
const crypto = require("crypto");
|
const crypto = require("crypto");
|
||||||
|
|
||||||
const ssrFile = "/app/apps/web/.output/server/_ssr/pdf-document-COfeOLVC.mjs";
|
const appDir = process.env.APP_DIR;
|
||||||
const publicFilenameFile = "/app/apps/web/.output/public/assets/file-D5WsIgJH.js";
|
const outputDir = path.join(appDir, ".output");
|
||||||
const publicPdfFile = "/app/apps/web/.output/public/assets/pdf-document-BplbXx-0.js";
|
const assetsDir = path.join(outputDir, "public/assets");
|
||||||
const serverIndexFile = "/app/apps/web/.output/server/index.mjs";
|
const ssrDir = path.join(outputDir, "server/_ssr");
|
||||||
|
const serverIndexFile = path.join(outputDir, "server/index.mjs");
|
||||||
const filenameCacheBust = "rr-filename-title-20260520";
|
const filenameCacheBust = "rr-filename-title-20260520";
|
||||||
const pdfCacheBust = "rr-glalie-layout-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 */";
|
const browserBufferPolyfill = "var Buffer=globalThis.Buffer??{isBuffer:()=>false,allocUnsafe:e=>new Uint8Array(e),alloc:e=>new Uint8Array(e)};/* rr-browser-buffer-polyfill */";
|
||||||
|
|
||||||
|
function warn(message) {
|
||||||
|
console.warn(`Reactive Resume runtime patch: ${message}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
function read(file) {
|
||||||
|
return fs.readFileSync(file, "utf8");
|
||||||
|
}
|
||||||
|
|
||||||
|
function write(file, source) {
|
||||||
|
fs.writeFileSync(file, source);
|
||||||
|
}
|
||||||
|
|
||||||
function makeEtag(buffer) {
|
function makeEtag(buffer) {
|
||||||
const digest = crypto.createHash("sha1").update(buffer).digest("base64").replace(/=+$/g, "");
|
const digest = crypto.createHash("sha1").update(buffer).digest("base64").replace(/=+$/g, "");
|
||||||
return `"${buffer.length.toString(16)}-${digest}"`;
|
return `"${buffer.length.toString(16)}-${digest}"`;
|
||||||
@@ -44,6 +76,17 @@ function patchStaticManifestEntry(source, urlPath, filePath) {
|
|||||||
return source.slice(0, start) + entry + source.slice(end);
|
return source.slice(0, start) + entry + source.slice(end);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function escapeRegex(value) {
|
||||||
|
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
||||||
|
}
|
||||||
|
|
||||||
|
function listJsFiles(dir) {
|
||||||
|
if (!fs.existsSync(dir)) return [];
|
||||||
|
return fs.readdirSync(dir)
|
||||||
|
.filter((name) => name.endsWith(".js"))
|
||||||
|
.map((name) => path.join(dir, name));
|
||||||
|
}
|
||||||
|
|
||||||
function replaceOnce(source, from, to) {
|
function replaceOnce(source, from, to) {
|
||||||
return source.includes(to) ? source : source.replace(from, to);
|
return source.includes(to) ? source : source.replace(from, to);
|
||||||
}
|
}
|
||||||
@@ -52,26 +95,35 @@ function replaceRegexOnce(source, regex, to) {
|
|||||||
return source.includes(to) ? source : source.replace(regex, to);
|
return source.includes(to) ? source : source.replace(regex, to);
|
||||||
}
|
}
|
||||||
|
|
||||||
function patchFilenameBundle() {
|
function patchFilenameBundle(file) {
|
||||||
let source = fs.readFileSync(publicFilenameFile, "utf8");
|
let source = read(file);
|
||||||
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}`:""}`}';
|
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)) {
|
if (source.includes(replacement)) return true;
|
||||||
|
|
||||||
const start = source.indexOf("function t(");
|
const start = source.indexOf("function t(");
|
||||||
const end = source.indexOf("function n(", start);
|
const end = source.indexOf("function n(", start);
|
||||||
if (start === -1 || end === -1) throw new Error("filename function marker not found");
|
if (start === -1 || end === -1) {
|
||||||
source = source.slice(0, start) + replacement + source.slice(end);
|
warn(`filename bundle marker not found in ${path.basename(file)}, skipped`);
|
||||||
fs.writeFileSync(publicFilenameFile, source);
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
source = source.slice(0, start) + replacement + source.slice(end);
|
||||||
|
write(file, source);
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
function patchSsr(source) {
|
function patchSsr(source) {
|
||||||
source = source.replace(/\n\t\tname: "",\n\t\tdata: \{/, "\n\t\tname: resume.name,\n\t\tdata: {");
|
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}`;
|
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)) {
|
if (!source.includes(filenameReplacement)) {
|
||||||
const start = source.indexOf("function generateFilename(");
|
const start = source.indexOf("function generateFilename(");
|
||||||
const end = source.indexOf("\nfunction downloadWithAnchor(", start);
|
const end = source.indexOf("\nfunction downloadWithAnchor(", start);
|
||||||
if (start === -1 || end === -1) throw new Error("SSR generateFilename marker not found");
|
if (start !== -1 && end !== -1) {
|
||||||
source = source.slice(0, start) + filenameReplacement + source.slice(end);
|
source = source.slice(0, start) + filenameReplacement + source.slice(end);
|
||||||
|
} else {
|
||||||
|
warn("SSR generateFilename marker not found, skipped");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
source = source
|
source = source
|
||||||
@@ -82,6 +134,7 @@ function patchSsr(source) {
|
|||||||
.replace(/style: composeStyles\(styles\.sidebarContent, \{ rowGap: metrics\.sectionGap \}\),/g, "style: composeStyles(styles.sidebarContent, { rowGap: 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(/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},");
|
.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 = replaceRegexOnce(
|
||||||
source,
|
source,
|
||||||
/sectionHeading: \{\s*borderBottomWidth: 1,\s*borderBottomColor: primary,\s*paddingBottom: 1(?:\.3)?\s*\},\s*item: \{ rowGap: metrics\.gapY\(\.125\) \},/,
|
/sectionHeading: \{\s*borderBottomWidth: 1,\s*borderBottomColor: primary,\s*paddingBottom: 1(?:\.3)?\s*\},\s*item: \{ rowGap: metrics\.gapY\(\.125\) \},/,
|
||||||
@@ -100,15 +153,16 @@ function patchSsr(source) {
|
|||||||
return source;
|
return source;
|
||||||
}
|
}
|
||||||
|
|
||||||
function ensureBrowserBufferPolyfill(source) {
|
function patchPublicPdf(source) {
|
||||||
if (source.includes("rr-browser-buffer-polyfill")) return source;
|
if (!source.includes("rr-browser-buffer-polyfill")) {
|
||||||
const insertAt = source.indexOf(";") + 1;
|
const insertAt = source.indexOf(";") + 1;
|
||||||
if (insertAt <= 0 || !source.startsWith("import")) throw new Error("PDF bundle import prelude not found");
|
if (insertAt > 0 && source.startsWith("import")) {
|
||||||
return source.slice(0, insertAt) + browserBufferPolyfill + source.slice(insertAt);
|
source = source.slice(0, insertAt) + browserBufferPolyfill + source.slice(insertAt);
|
||||||
|
} else {
|
||||||
|
warn("PDF bundle import prelude not found, Buffer shim skipped");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function patchPublicPdf(source) {
|
|
||||||
source = ensureBrowserBufferPolyfill(source);
|
|
||||||
source = 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(/([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\(3\.5\)/g, (m) => `${m[0]}.gapY(3.0)`)
|
||||||
@@ -118,43 +172,80 @@ function patchPublicPdf(source) {
|
|||||||
.replace(/style:\$\(a\.mainContent,\{rowGap:o\.sectionGap\}\)/g, "style:$(a.mainContent,{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)?)?\}/, "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)}");
|
.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, "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}");
|
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;
|
return source;
|
||||||
}
|
}
|
||||||
|
|
||||||
function patchImporters() {
|
const assetFiles = listJsFiles(assetsDir);
|
||||||
const assetsDir = "/app/apps/web/.output/public/assets";
|
const filenameFiles = assetFiles
|
||||||
const files = fs.readdirSync(assetsDir).filter((name) => name.endsWith(".js")).map((name) => `${assetsDir}/${name}`);
|
.filter((file) => {
|
||||||
const touched = [];
|
const source = read(file);
|
||||||
for (const file of files) {
|
return source.includes("URL.createObjectURL") && source.includes(".download");
|
||||||
let source = fs.readFileSync(file, "utf8");
|
})
|
||||||
let next = source
|
.sort((a, b) => fs.statSync(a).size - fs.statSync(b).size);
|
||||||
.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}`);
|
const patchedFilenameFiles = [];
|
||||||
|
for (const file of filenameFiles) {
|
||||||
|
if (patchFilenameBundle(file)) patchedFilenameFiles.push(file);
|
||||||
|
}
|
||||||
|
if (patchedFilenameFiles.length === 0) warn("no filename bundle patched");
|
||||||
|
|
||||||
|
let ssrFile = "";
|
||||||
|
if (fs.existsSync(ssrDir)) {
|
||||||
|
ssrFile = fs.readdirSync(ssrDir)
|
||||||
|
.filter((name) => name.endsWith(".mjs"))
|
||||||
|
.map((name) => path.join(ssrDir, name))
|
||||||
|
.find((file) => read(file).includes("function generateFilename(")) || "";
|
||||||
|
}
|
||||||
|
if (ssrFile) {
|
||||||
|
write(ssrFile, patchSsr(read(ssrFile)));
|
||||||
|
} else {
|
||||||
|
warn("SSR bundle with generateFilename not found");
|
||||||
|
}
|
||||||
|
|
||||||
|
const pdfFile = assetFiles
|
||||||
|
.filter((file) => path.basename(file).startsWith("pdf-document-"))
|
||||||
|
.sort((a, b) => fs.statSync(b).size - fs.statSync(a).size)[0] || "";
|
||||||
|
if (pdfFile) {
|
||||||
|
write(pdfFile, patchPublicPdf(read(pdfFile)));
|
||||||
|
} else {
|
||||||
|
warn("public PDF bundle not found");
|
||||||
|
}
|
||||||
|
|
||||||
|
const filenameBases = patchedFilenameFiles.map((file) => path.basename(file));
|
||||||
|
const pdfBase = pdfFile ? path.basename(pdfFile) : "";
|
||||||
|
const touchedImporters = [];
|
||||||
|
for (const file of assetFiles) {
|
||||||
|
let source = read(file);
|
||||||
|
let next = source;
|
||||||
|
for (const base of filenameBases) {
|
||||||
|
next = next.replace(new RegExp(`\\./${escapeRegex(base)}(?:\\?v=rr-filename-[A-Za-z0-9-]+)?`, "g"), `./${base}?v=${filenameCacheBust}`);
|
||||||
|
}
|
||||||
|
if (pdfBase) {
|
||||||
|
next = next.replace(new RegExp(`\\./${escapeRegex(pdfBase)}(?:\\?v=rr-[^"'\\\`]+)?`, "g"), `./${pdfBase}?v=${pdfCacheBust}`);
|
||||||
|
}
|
||||||
if (next !== source) {
|
if (next !== source) {
|
||||||
fs.writeFileSync(file, next);
|
write(file, next);
|
||||||
touched.push(file);
|
touchedImporters.push(file);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return touched;
|
|
||||||
}
|
|
||||||
|
|
||||||
patchFilenameBundle();
|
if (fs.existsSync(serverIndexFile)) {
|
||||||
fs.writeFileSync(ssrFile, patchSsr(fs.readFileSync(ssrFile, "utf8")));
|
let serverIndex = read(serverIndexFile);
|
||||||
fs.writeFileSync(publicPdfFile, patchPublicPdf(fs.readFileSync(publicPdfFile, "utf8")));
|
for (const file of [...patchedFilenameFiles, pdfFile, ...touchedImporters].filter(Boolean)) {
|
||||||
const importers = patchImporters();
|
serverIndex = patchStaticManifestEntry(serverIndex, `/assets/${path.basename(file)}`, file);
|
||||||
|
}
|
||||||
let serverIndex = fs.readFileSync(serverIndexFile, "utf8");
|
write(serverIndexFile, serverIndex);
|
||||||
serverIndex = patchStaticManifestEntry(serverIndex, "/assets/file-D5WsIgJH.js", publicFilenameFile);
|
}
|
||||||
serverIndex = patchStaticManifestEntry(serverIndex, "/assets/pdf-document-BplbXx-0.js", publicPdfFile);
|
|
||||||
for (const file of importers) {
|
for (const file of [...patchedFilenameFiles, pdfFile, ssrFile, serverIndexFile].filter(Boolean)) {
|
||||||
serverIndex = patchStaticManifestEntry(serverIndex, `/assets/${file.split("/").pop()}`, file);
|
try {
|
||||||
|
new Function(read(file));
|
||||||
|
} catch {
|
||||||
|
// ESM/import bundles are validated by Node at application startup; keep this
|
||||||
|
// best-effort so a harmless syntax-check limitation never blocks boot.
|
||||||
|
}
|
||||||
}
|
}
|
||||||
fs.writeFileSync(serverIndexFile, serverIndex);
|
|
||||||
NODE
|
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
|
|
||||||
|
|||||||
@@ -4,8 +4,8 @@
|
|||||||
"email": "zub572701190@stu.xjtu.edu.cn",
|
"email": "zub572701190@stu.xjtu.edu.cn",
|
||||||
"phone": "+86 139-4611-2059",
|
"phone": "+86 139-4611-2059",
|
||||||
"website": {
|
"website": {
|
||||||
"url": "https://isiseg.huijutec.cn/audience/resume",
|
"url": "https://me.huijutec.cn/audience/resume",
|
||||||
"label": "isiseg.huijutec.cn/audience/resume"
|
"label": "me.huijutec.cn/audience/resume"
|
||||||
},
|
},
|
||||||
"headline": "AI 医工交叉博士|智能外科与微创手术导航|多模态大模型与临床转化",
|
"headline": "AI 医工交叉博士|智能外科与微创手术导航|多模态大模型与临床转化",
|
||||||
"location": "陕西西安|西安交通大学",
|
"location": "陕西西安|西安交通大学",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
-- Reactive Resume personal seed data.
|
-- Reactive Resume personal seed data.
|
||||||
-- Generated from the current resume and bundled uploads so fresh deployments can open:
|
-- Generated from the current resume and bundled uploads so fresh deployments can open:
|
||||||
-- https://isiseg.huijutec.cn/audience/resume
|
-- https://me.huijutec.cn/audience/resume
|
||||||
|
|
||||||
BEGIN;
|
BEGIN;
|
||||||
|
|
||||||
@@ -66,8 +66,8 @@ INSERT INTO resume (
|
|||||||
"email": "zub572701190@stu.xjtu.edu.cn",
|
"email": "zub572701190@stu.xjtu.edu.cn",
|
||||||
"phone": "+86 139-4611-2059",
|
"phone": "+86 139-4611-2059",
|
||||||
"website": {
|
"website": {
|
||||||
"url": "https://isiseg.huijutec.cn/audience/resume",
|
"url": "https://me.huijutec.cn/audience/resume",
|
||||||
"label": "isiseg.huijutec.cn/audience/resume"
|
"label": "me.huijutec.cn/audience/resume"
|
||||||
},
|
},
|
||||||
"headline": "AI 医工交叉博士|智能外科与微创手术导航|多模态大模型与临床转化",
|
"headline": "AI 医工交叉博士|智能外科与微创手术导航|多模态大模型与临床转化",
|
||||||
"location": "陕西西安|西安交通大学",
|
"location": "陕西西安|西安交通大学",
|
||||||
|
|||||||
@@ -48,6 +48,11 @@ services:
|
|||||||
command:
|
command:
|
||||||
- |
|
- |
|
||||||
sh /opt/reactive-resume-patches/reactive-resume-runtime-patch.sh
|
sh /opt/reactive-resume-patches/reactive-resume-runtime-patch.sh
|
||||||
|
APP_DIR="$(cat /tmp/reactive-resume-app-dir 2>/dev/null || true)"
|
||||||
|
if [ -z "$$APP_DIR" ]; then
|
||||||
|
APP_DIR="$(find /app -path '*/.output/server/index.mjs' -type f 2>/dev/null | head -n 1 | sed 's#/.output/server/index.mjs##')"
|
||||||
|
fi
|
||||||
|
cd "$${APP_DIR:-/app/apps/web}"
|
||||||
exec node .output/server/index.mjs
|
exec node .output/server/index.mjs
|
||||||
depends_on:
|
depends_on:
|
||||||
reactive_resume_permissions:
|
reactive_resume_permissions:
|
||||||
|
|||||||
@@ -1,25 +1,57 @@
|
|||||||
#!/bin/sh
|
#!/bin/sh
|
||||||
set -eu
|
set -eu
|
||||||
|
|
||||||
SSR_FILE="/app/apps/web/.output/server/_ssr/pdf-document-COfeOLVC.mjs"
|
APP_DIR="${REACTIVE_RESUME_APP_DIR:-}"
|
||||||
PUBLIC_FILENAME_FILE="/app/apps/web/.output/public/assets/file-D5WsIgJH.js"
|
if [ -z "$APP_DIR" ]; then
|
||||||
PUBLIC_PDF_FILE="/app/apps/web/.output/public/assets/pdf-document-BplbXx-0.js"
|
for candidate in /app/apps/web /app; do
|
||||||
SERVER_INDEX_FILE="/app/apps/web/.output/server/index.mjs"
|
if [ -f "$candidate/.output/server/index.mjs" ]; then
|
||||||
CACHE_BUST_FILENAME="rr-filename-title-20260520"
|
APP_DIR="$candidate"
|
||||||
CACHE_BUST_PDF="rr-glalie-layout-20260520"
|
break
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -z "$APP_DIR" ]; then
|
||||||
|
index_file="$(find /app -path "*/.output/server/index.mjs" -type f 2>/dev/null | head -n 1 || true)"
|
||||||
|
if [ -n "$index_file" ]; then
|
||||||
|
APP_DIR="${index_file%/.output/server/index.mjs}"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -z "$APP_DIR" ] || [ ! -f "$APP_DIR/.output/server/index.mjs" ]; then
|
||||||
|
echo "Reactive Resume runtime patch skipped: .output/server/index.mjs not found under /app" >&2
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
printf "%s" "$APP_DIR" > /tmp/reactive-resume-app-dir
|
||||||
|
export APP_DIR
|
||||||
|
|
||||||
node - <<'NODE'
|
node - <<'NODE'
|
||||||
const fs = require("fs");
|
const fs = require("fs");
|
||||||
|
const path = require("path");
|
||||||
const crypto = require("crypto");
|
const crypto = require("crypto");
|
||||||
|
|
||||||
const ssrFile = "/app/apps/web/.output/server/_ssr/pdf-document-COfeOLVC.mjs";
|
const appDir = process.env.APP_DIR;
|
||||||
const publicFilenameFile = "/app/apps/web/.output/public/assets/file-D5WsIgJH.js";
|
const outputDir = path.join(appDir, ".output");
|
||||||
const publicPdfFile = "/app/apps/web/.output/public/assets/pdf-document-BplbXx-0.js";
|
const assetsDir = path.join(outputDir, "public/assets");
|
||||||
const serverIndexFile = "/app/apps/web/.output/server/index.mjs";
|
const ssrDir = path.join(outputDir, "server/_ssr");
|
||||||
|
const serverIndexFile = path.join(outputDir, "server/index.mjs");
|
||||||
const filenameCacheBust = "rr-filename-title-20260520";
|
const filenameCacheBust = "rr-filename-title-20260520";
|
||||||
const pdfCacheBust = "rr-glalie-layout-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 */";
|
const browserBufferPolyfill = "var Buffer=globalThis.Buffer??{isBuffer:()=>false,allocUnsafe:e=>new Uint8Array(e),alloc:e=>new Uint8Array(e)};/* rr-browser-buffer-polyfill */";
|
||||||
|
|
||||||
|
function warn(message) {
|
||||||
|
console.warn(`Reactive Resume runtime patch: ${message}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
function read(file) {
|
||||||
|
return fs.readFileSync(file, "utf8");
|
||||||
|
}
|
||||||
|
|
||||||
|
function write(file, source) {
|
||||||
|
fs.writeFileSync(file, source);
|
||||||
|
}
|
||||||
|
|
||||||
function makeEtag(buffer) {
|
function makeEtag(buffer) {
|
||||||
const digest = crypto.createHash("sha1").update(buffer).digest("base64").replace(/=+$/g, "");
|
const digest = crypto.createHash("sha1").update(buffer).digest("base64").replace(/=+$/g, "");
|
||||||
return `"${buffer.length.toString(16)}-${digest}"`;
|
return `"${buffer.length.toString(16)}-${digest}"`;
|
||||||
@@ -44,6 +76,17 @@ function patchStaticManifestEntry(source, urlPath, filePath) {
|
|||||||
return source.slice(0, start) + entry + source.slice(end);
|
return source.slice(0, start) + entry + source.slice(end);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function escapeRegex(value) {
|
||||||
|
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
||||||
|
}
|
||||||
|
|
||||||
|
function listJsFiles(dir) {
|
||||||
|
if (!fs.existsSync(dir)) return [];
|
||||||
|
return fs.readdirSync(dir)
|
||||||
|
.filter((name) => name.endsWith(".js"))
|
||||||
|
.map((name) => path.join(dir, name));
|
||||||
|
}
|
||||||
|
|
||||||
function replaceOnce(source, from, to) {
|
function replaceOnce(source, from, to) {
|
||||||
return source.includes(to) ? source : source.replace(from, to);
|
return source.includes(to) ? source : source.replace(from, to);
|
||||||
}
|
}
|
||||||
@@ -52,26 +95,35 @@ function replaceRegexOnce(source, regex, to) {
|
|||||||
return source.includes(to) ? source : source.replace(regex, to);
|
return source.includes(to) ? source : source.replace(regex, to);
|
||||||
}
|
}
|
||||||
|
|
||||||
function patchFilenameBundle() {
|
function patchFilenameBundle(file) {
|
||||||
let source = fs.readFileSync(publicFilenameFile, "utf8");
|
let source = read(file);
|
||||||
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}`:""}`}';
|
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)) {
|
if (source.includes(replacement)) return true;
|
||||||
|
|
||||||
const start = source.indexOf("function t(");
|
const start = source.indexOf("function t(");
|
||||||
const end = source.indexOf("function n(", start);
|
const end = source.indexOf("function n(", start);
|
||||||
if (start === -1 || end === -1) throw new Error("filename function marker not found");
|
if (start === -1 || end === -1) {
|
||||||
source = source.slice(0, start) + replacement + source.slice(end);
|
warn(`filename bundle marker not found in ${path.basename(file)}, skipped`);
|
||||||
fs.writeFileSync(publicFilenameFile, source);
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
source = source.slice(0, start) + replacement + source.slice(end);
|
||||||
|
write(file, source);
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
function patchSsr(source) {
|
function patchSsr(source) {
|
||||||
source = source.replace(/\n\t\tname: "",\n\t\tdata: \{/, "\n\t\tname: resume.name,\n\t\tdata: {");
|
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}`;
|
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)) {
|
if (!source.includes(filenameReplacement)) {
|
||||||
const start = source.indexOf("function generateFilename(");
|
const start = source.indexOf("function generateFilename(");
|
||||||
const end = source.indexOf("\nfunction downloadWithAnchor(", start);
|
const end = source.indexOf("\nfunction downloadWithAnchor(", start);
|
||||||
if (start === -1 || end === -1) throw new Error("SSR generateFilename marker not found");
|
if (start !== -1 && end !== -1) {
|
||||||
source = source.slice(0, start) + filenameReplacement + source.slice(end);
|
source = source.slice(0, start) + filenameReplacement + source.slice(end);
|
||||||
|
} else {
|
||||||
|
warn("SSR generateFilename marker not found, skipped");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
source = source
|
source = source
|
||||||
@@ -82,6 +134,7 @@ function patchSsr(source) {
|
|||||||
.replace(/style: composeStyles\(styles\.sidebarContent, \{ rowGap: metrics\.sectionGap \}\),/g, "style: composeStyles(styles.sidebarContent, { rowGap: 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(/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},");
|
.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 = replaceRegexOnce(
|
||||||
source,
|
source,
|
||||||
/sectionHeading: \{\s*borderBottomWidth: 1,\s*borderBottomColor: primary,\s*paddingBottom: 1(?:\.3)?\s*\},\s*item: \{ rowGap: metrics\.gapY\(\.125\) \},/,
|
/sectionHeading: \{\s*borderBottomWidth: 1,\s*borderBottomColor: primary,\s*paddingBottom: 1(?:\.3)?\s*\},\s*item: \{ rowGap: metrics\.gapY\(\.125\) \},/,
|
||||||
@@ -100,15 +153,16 @@ function patchSsr(source) {
|
|||||||
return source;
|
return source;
|
||||||
}
|
}
|
||||||
|
|
||||||
function ensureBrowserBufferPolyfill(source) {
|
function patchPublicPdf(source) {
|
||||||
if (source.includes("rr-browser-buffer-polyfill")) return source;
|
if (!source.includes("rr-browser-buffer-polyfill")) {
|
||||||
const insertAt = source.indexOf(";") + 1;
|
const insertAt = source.indexOf(";") + 1;
|
||||||
if (insertAt <= 0 || !source.startsWith("import")) throw new Error("PDF bundle import prelude not found");
|
if (insertAt > 0 && source.startsWith("import")) {
|
||||||
return source.slice(0, insertAt) + browserBufferPolyfill + source.slice(insertAt);
|
source = source.slice(0, insertAt) + browserBufferPolyfill + source.slice(insertAt);
|
||||||
|
} else {
|
||||||
|
warn("PDF bundle import prelude not found, Buffer shim skipped");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function patchPublicPdf(source) {
|
|
||||||
source = ensureBrowserBufferPolyfill(source);
|
|
||||||
source = 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(/([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\(3\.5\)/g, (m) => `${m[0]}.gapY(3.0)`)
|
||||||
@@ -118,43 +172,80 @@ function patchPublicPdf(source) {
|
|||||||
.replace(/style:\$\(a\.mainContent,\{rowGap:o\.sectionGap\}\)/g, "style:$(a.mainContent,{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)?)?\}/, "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)}");
|
.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, "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}");
|
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;
|
return source;
|
||||||
}
|
}
|
||||||
|
|
||||||
function patchImporters() {
|
const assetFiles = listJsFiles(assetsDir);
|
||||||
const assetsDir = "/app/apps/web/.output/public/assets";
|
const filenameFiles = assetFiles
|
||||||
const files = fs.readdirSync(assetsDir).filter((name) => name.endsWith(".js")).map((name) => `${assetsDir}/${name}`);
|
.filter((file) => {
|
||||||
const touched = [];
|
const source = read(file);
|
||||||
for (const file of files) {
|
return source.includes("URL.createObjectURL") && source.includes(".download");
|
||||||
let source = fs.readFileSync(file, "utf8");
|
})
|
||||||
let next = source
|
.sort((a, b) => fs.statSync(a).size - fs.statSync(b).size);
|
||||||
.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}`);
|
const patchedFilenameFiles = [];
|
||||||
|
for (const file of filenameFiles) {
|
||||||
|
if (patchFilenameBundle(file)) patchedFilenameFiles.push(file);
|
||||||
|
}
|
||||||
|
if (patchedFilenameFiles.length === 0) warn("no filename bundle patched");
|
||||||
|
|
||||||
|
let ssrFile = "";
|
||||||
|
if (fs.existsSync(ssrDir)) {
|
||||||
|
ssrFile = fs.readdirSync(ssrDir)
|
||||||
|
.filter((name) => name.endsWith(".mjs"))
|
||||||
|
.map((name) => path.join(ssrDir, name))
|
||||||
|
.find((file) => read(file).includes("function generateFilename(")) || "";
|
||||||
|
}
|
||||||
|
if (ssrFile) {
|
||||||
|
write(ssrFile, patchSsr(read(ssrFile)));
|
||||||
|
} else {
|
||||||
|
warn("SSR bundle with generateFilename not found");
|
||||||
|
}
|
||||||
|
|
||||||
|
const pdfFile = assetFiles
|
||||||
|
.filter((file) => path.basename(file).startsWith("pdf-document-"))
|
||||||
|
.sort((a, b) => fs.statSync(b).size - fs.statSync(a).size)[0] || "";
|
||||||
|
if (pdfFile) {
|
||||||
|
write(pdfFile, patchPublicPdf(read(pdfFile)));
|
||||||
|
} else {
|
||||||
|
warn("public PDF bundle not found");
|
||||||
|
}
|
||||||
|
|
||||||
|
const filenameBases = patchedFilenameFiles.map((file) => path.basename(file));
|
||||||
|
const pdfBase = pdfFile ? path.basename(pdfFile) : "";
|
||||||
|
const touchedImporters = [];
|
||||||
|
for (const file of assetFiles) {
|
||||||
|
let source = read(file);
|
||||||
|
let next = source;
|
||||||
|
for (const base of filenameBases) {
|
||||||
|
next = next.replace(new RegExp(`\\./${escapeRegex(base)}(?:\\?v=rr-filename-[A-Za-z0-9-]+)?`, "g"), `./${base}?v=${filenameCacheBust}`);
|
||||||
|
}
|
||||||
|
if (pdfBase) {
|
||||||
|
next = next.replace(new RegExp(`\\./${escapeRegex(pdfBase)}(?:\\?v=rr-[^"'\\\`]+)?`, "g"), `./${pdfBase}?v=${pdfCacheBust}`);
|
||||||
|
}
|
||||||
if (next !== source) {
|
if (next !== source) {
|
||||||
fs.writeFileSync(file, next);
|
write(file, next);
|
||||||
touched.push(file);
|
touchedImporters.push(file);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return touched;
|
|
||||||
}
|
|
||||||
|
|
||||||
patchFilenameBundle();
|
if (fs.existsSync(serverIndexFile)) {
|
||||||
fs.writeFileSync(ssrFile, patchSsr(fs.readFileSync(ssrFile, "utf8")));
|
let serverIndex = read(serverIndexFile);
|
||||||
fs.writeFileSync(publicPdfFile, patchPublicPdf(fs.readFileSync(publicPdfFile, "utf8")));
|
for (const file of [...patchedFilenameFiles, pdfFile, ...touchedImporters].filter(Boolean)) {
|
||||||
const importers = patchImporters();
|
serverIndex = patchStaticManifestEntry(serverIndex, `/assets/${path.basename(file)}`, file);
|
||||||
|
}
|
||||||
let serverIndex = fs.readFileSync(serverIndexFile, "utf8");
|
write(serverIndexFile, serverIndex);
|
||||||
serverIndex = patchStaticManifestEntry(serverIndex, "/assets/file-D5WsIgJH.js", publicFilenameFile);
|
}
|
||||||
serverIndex = patchStaticManifestEntry(serverIndex, "/assets/pdf-document-BplbXx-0.js", publicPdfFile);
|
|
||||||
for (const file of importers) {
|
for (const file of [...patchedFilenameFiles, pdfFile, ssrFile, serverIndexFile].filter(Boolean)) {
|
||||||
serverIndex = patchStaticManifestEntry(serverIndex, `/assets/${file.split("/").pop()}`, file);
|
try {
|
||||||
|
new Function(read(file));
|
||||||
|
} catch {
|
||||||
|
// ESM/import bundles are validated by Node at application startup; keep this
|
||||||
|
// best-effort so a harmless syntax-check limitation never blocks boot.
|
||||||
|
}
|
||||||
}
|
}
|
||||||
fs.writeFileSync(serverIndexFile, serverIndex);
|
|
||||||
NODE
|
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
|
|
||||||
|
|||||||
Reference in New Issue
Block a user