From 1d9e0ee1efb65b1eebd68f91d16f9e9757f76e24 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Mon, 27 Apr 2026 16:51:11 +0000 Subject: [PATCH] =?UTF-8?q?fix(leaderboard):=20fallback=20=E9=9D=9E?= =?UTF-8?q?=E6=95=B0=E7=BB=84=E5=86=85=E5=AE=B9=E5=BF=85=E9=A1=BB=E8=A6=86?= =?UTF-8?q?=E7=9B=96=E7=A9=BA=EF=BC=8C=E4=B8=8D=E8=83=BD=20preserve?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Copilot CR (PR #326) 反馈 之前 fallback 只要 JSON.parse 成功就 preserveExisting=true,包括对象、null 等非数组类型也会被"维持原状"。但下游有多处 `import leaderboardData from "@/generated/site-leaderboard.json"` 直接 .filter/.map(如 Hero 的 top3、 /rank 页),一旦内容不是数组,整个 Next build 就会因 "filter is not a function" 直接挂掉。 修法 四档 fallback: 1. 非空数组 → 保留(warn 多少条) 2. 空数组 → 保留空数组(语义合法,下游 .filter 不挂) 3. JSON 损坏 / 非数组 → 兜底覆盖为 [](避免下游 import 后 type error) 4. 文件不存在 → 兜底覆盖为 [] 效果 - 任何已存在的合法数组都不被无故覆盖 - 任何非数组数据都强制规范化为 [],下游 .filter/.map 永远 safe --- scripts/generate-leaderboard.mjs | 33 ++++++++++++++++++++++---------- 1 file changed, 23 insertions(+), 10 deletions(-) diff --git a/scripts/generate-leaderboard.mjs b/scripts/generate-leaderboard.mjs index 9b493d1..2172bf0 100644 --- a/scripts/generate-leaderboard.mjs +++ b/scripts/generate-leaderboard.mjs @@ -157,25 +157,38 @@ async function main() { // 一次 fetch 失败(CF 临时挑战 / Vercel runner IP 信誉低 / 后端短暂抖动) // 不应该把 commit 进 git 的好数据冲成空数组上线。 // - // 三种情况: - // 1. 文件已存在 + 内容是非空数组 → 保留旧数据 exit 0 - // 2. 文件已存在但是空数组 / 不是数组 → 维持原状 exit 0(不主动覆盖) - // 3. 文件不存在(首次 build / 干净 cache)→ 写空数组兜底 exit 0 + // 四种情况: + // 1. 文件已存在 + 是非空数组 → 保留旧数据 exit 0 + // 2. 文件已存在 + 是空数组 → 保留空数组 exit 0(语义合法,下游 .filter 不挂) + // 3. 文件已存在 + JSON 损坏 / 非数组 → 视为无效,走兜底写 [] 覆盖 + // 4. 文件不存在(首次 build) → 走兜底写 [] 覆盖 + // + // 关键约束:下游有多处 import leaderboard 后直接 .filter/.map, + // 一旦内容是 null/object 等非数组类型,整个 Next build 会因 + // "filter is not a function" 挂掉。所以"非数组"必须强制覆盖空数组, + // 不能跟"空数组"一样走 preserve 分支。 let preservedExisting = false; try { const existing = await fs.readFile(outputAbs, "utf-8"); try { const parsed = JSON.parse(existing); - if (Array.isArray(parsed) && parsed.length > 0) { - console.warn( - `[generate-leaderboard] 后端不可用,但保留 ${OUTPUT} 已有 ${parsed.length} 条数据,不覆盖。 | Backend unreachable; keeping existing leaderboard with ${parsed.length} entries.`, - ); + if (Array.isArray(parsed)) { + if (parsed.length > 0) { + console.warn( + `[generate-leaderboard] 后端不可用,但保留 ${OUTPUT} 已有 ${parsed.length} 条数据,不覆盖。 | Backend unreachable; keeping existing leaderboard with ${parsed.length} entries.`, + ); + } else { + console.warn( + `[generate-leaderboard] 后端不可用,但保留 ${OUTPUT} 的空数组内容。 | Backend unreachable; keeping existing empty leaderboard array.`, + ); + } + preservedExisting = true; } else { + // 非数组(对象、字面量、null 等)→ 不 preserve,走下方兜底覆盖空数组 console.warn( - `[generate-leaderboard] 后端不可用,且 ${OUTPUT} 已有内容非有效非空数组,维持原状。`, + `[generate-leaderboard] ${OUTPUT} 已存在但内容不是数组(typeof=${typeof parsed}),按无效数据处理,兜底覆盖为 []。`, ); } - preservedExisting = true; } catch { // 文件存在但 JSON 损坏:当作没有,走下面写空兜底 console.warn(