feat(deploy): 前端源码更新「仅为注释」时不再卡住部署
## 起因(实测,同一天两次)
`redeploy-gateway.sh` 用 mtime 比对源码与 dist:
newer=$(find src -type f -newer dist/index.html)
[ -n "$newer" ] && exit 1
mtime 只说「这个文件被碰过」,不说「它变了什么」。于是共享工作树里**任何人
改一行注释就会拦下部署** —— 那行注释不进 bundle,重建产物与现有 dist
逐字节相同。
2026-10-02 实测被拦两次,都是 `client/electron/src/api/client.ts` 的线程树
说明注释(另一会话的工作树在制品,未提交),每次多花一轮 `npm run build`。
## 为什么不是拆掉那道闸
2026-09-14 踩过它的来历:改了 `src/lib/appearance.ts` 的请求路径却没跑
vite build,部署照样「同步成功」,出去的还是旧 bundle —— 表现为接口 404
(`/api/v1/api/v1/…` 双前缀),而**所有单测都是绿的**。那种失败是沉默的,
所以闸不能拆。
本改动是在闸**前面**加一层精化:先问「差异是否只是注释」,是则放行并说明,
否则维持拦截。拿不准时一律偏向拦截:
误拦的代价 = 多构建一次(几十秒)
误放的代价 = 把旧界面打进二进制(接口 404,且没人立刻归因)
## 判定口径(deploy/web-comment-only.mjs)
对每个「比 dist 新」的文件取它相对 **HEAD** 的 diff,去掉 `---`/`+++` 头、
diff 元信息与整行注释后若还剩内容 ⇒ 真改动 ⇒ 拦截。
- **只看未提交差异**(`git diff HEAD --`)。已提交改动早于本次部署决策。
- **未跟踪的新文件**按真改动处理 —— 判不出就别放行。
- 滤的是「整行都是注释」的行;行尾注释(`code(); // 注释`)算真改动(保守)。
- 块注释中间行(` * …`)与结尾也算注释。
## 判据(7 格)
`client/electron/test/web-comment-only.test.mjs`。判据本身必须能区分
「仅注释」与「真代码」,所以每格都给**两侧**对照。其中「只有注释差异」
那格用的 diff **逐字取自当天实测的 git diff**。
**变异验证**:
去掉注释分支(所有行算真改动) → 红 7(部署会被那一行注释继续拦)
isCodeChange 恒 false → 红 7(把 2026-09-14 的静默失败放回来)
第二条是关键:它证明这套判据不会为了「少拦一次」而牺牲那道沉默失败的闸。
## 真实场景验证(不是只跑单测)
把 dist/index.html 时间戳改早 → 5 个源文件「比 dist 新」
⇒ commentOnly=true,理由写明「差异仅为注释」
临时往 sse.ts 追加一行真代码
⇒ commentOnly=false,理由点名那行 `+const __probe = 1;`
bash deploy/redeploy-gateway.sh --dry-run
⇒ [WARN] 前端源码被更新,但差异**仅为注释** ⇒ 不重建
## 顺带
`find … | head -20`(原 head -3):文件多时不至于只看到前 3 个就下结论。
This commit is contained in:
@ -147,13 +147,34 @@ if [ "$SYNC_WEB" = 1 ] && [ -d "$REPO/client/electron/dist/assets" ]; then
|
||||
# 部署脚本照样"同步成功",服务出去的还是旧 bundle —— 表现为接口 404
|
||||
# (/api/v1/api/v1/… 双前缀),而所有单测都是绿的。
|
||||
if [ -d "$REPO/client/electron/src" ] && [ -d "$REPO/client/electron/dist" ]; then
|
||||
newer=$(find "$REPO/client/electron/src" -type f -newer "$REPO/client/electron/dist/index.html" 2>/dev/null | head -3)
|
||||
newer=$(find "$REPO/client/electron/src" -type f -newer "$REPO/client/electron/dist/index.html" 2>/dev/null | head -20)
|
||||
if [ -n "$newer" ]; then
|
||||
echo " [FAIL] 前端 dist 比源码旧(先跑:cd client/electron && npm run build)"
|
||||
echo " 更新的文件:$(echo "$newer" | tr '\n' ' ')"
|
||||
exit 1
|
||||
# ★ 2026-10-02:mtime 只能说明「文件被碰过」,不能说明「它变了什么」。
|
||||
#
|
||||
# 共享工作树里别人改一行**注释**就会卡住部署 —— 那行注释不进 bundle,
|
||||
# 重建产物与现有 dist 逐字节相同。实测同一天因此多花了两轮 npm run build
|
||||
# (client/electron/src/api/client.ts 的线程树说明,另一会话的在制品)。
|
||||
#
|
||||
# 但 mtime 那道闸不能拆:2026-09-14 改了 appearance.ts 的请求路径却没重建,
|
||||
# 部署照样「同步成功」,出去的还是旧 bundle —— 表现为接口 404
|
||||
# (/api/v1/api/v1/…)而**所有单测都是绿的**。那种失败是沉默的。
|
||||
#
|
||||
# 所以:先问「差异是否只是注释」,是则放行,否则维持拦截。
|
||||
# 拿不准时偏向拦截(误拦 = 多构建一次;误放 = 把旧界面打进二进制)。
|
||||
verdict=$(REPO="$REPO" node "$REPO/deploy/web-comment-only.mjs" $newer 2>/dev/null)
|
||||
if [ "$(printf '%s' "$verdict" | sed -n 's/.*"commentOnly":\([a-z]*\).*/\1/p')" = "true" ]; then
|
||||
warn "前端源码被更新,但差异**仅为注释** ⇒ 不重建"
|
||||
printf ' %s\n' "$(printf '%s' "$verdict" | sed -n 's/.*"reason":"\([^"]*\)".*/\1/p')"
|
||||
warn "(产物内容不受影响;下次真改代码时仍需 npm run build)"
|
||||
else
|
||||
echo " [FAIL] 前端 dist 比源码旧(先跑:cd client/electron && npm run build)"
|
||||
echo " 更新的文件:$(echo "$newer" | tr '\n' ' ')"
|
||||
[ -n "$verdict" ] && echo " 原因:$(printf '%s' "$verdict" | sed -n 's/.*"reason":"\([^"]*\)".*/\1/p')"
|
||||
exit 1
|
||||
fi
|
||||
else
|
||||
echo " [ OK ] 前端产物比源码新"
|
||||
fi
|
||||
echo " [ OK ] 前端产物比源码新"
|
||||
fi
|
||||
else
|
||||
say "1. 跳过前端同步"
|
||||
|
||||
Reference in New Issue
Block a user