#!/usr/bin/env bash # 把「本次改动涉及哪些文件」映射成「需要重建哪些镜像」。 # # 用法: # printf '%s\n' "apps/backend/src/x.ts" | bash deploy/lib/classify-changes.sh # bash deploy/lib/classify-changes.sh --git-diff [repo] # 打印改动的文件列表 # # 输出:空格分隔的服务名(backend / web),两端都不用重建时输出空行。 # # 规则刻意保守——只有"明确属于单端、且进不了另一侧镜像"的改动才允许只重建一端, # 其余一律两端都重建,宁可多建一次也不允许"改了却没生效"。 # 判断依据是各 Dockerfile 的 COPY 范围: # backend 镜像 = apps/backend/**(排除 .dockerignore 里的 node_modules/dist/data) # web 镜像 = apps/frontend/** + deploy/caddy/Caddyfile # 取改动文件列表。必须关掉 core.quotePath: # 否则中文路径会输出成 "docs/\351\203\250..." 这种 C 转义带引号的形式, # 匹配不到 docs/* 之类的规则、一路掉进"两端都重建"的兜底分支 # (2026-10-03 真实踩过一次:只改文档却重建了 backend + web)。 print_git_diff() { local from="$1" to="$2" repo="${3:-.}" git -C "${repo}" -c core.quotePath=false diff --name-only "${from}" "${to}" } classify_changed_paths() { local backend=0 web=0 path local -a out=() # `|| [[ -n "${path}" ]]` 兜住"最后一行没有换行符"的情况(read 在 EOF 会返回非零) while IFS= read -r path || [[ -n "${path}" ]]; do # 去掉 git 输出里可能带的空白与残留引号 path="${path#"${path%%[![:space:]]*}"}" path="${path%"${path##*[![:space:]]}"}" path="${path#\"}" path="${path%\"}" [[ -n "${path}" ]] || continue case "${path}" in # 只改测试:生产镜像里不会执行任何 *.test.ts(tsconfig.build.json 把它们排除在 dist 之外, # 且生产不会跑 npm test),不需要为了一个测试文件重建镜像。 apps/backend/*.test.ts | apps/backend/*.test.js) ;; apps/frontend/*.test.ts | apps/frontend/*.test.tsx | apps/frontend/*.test.js) ;; # 明确的单端改动 apps/backend/*) backend=1 ;; apps/frontend/*) web=1 ;; deploy/docker/*dev*.Dockerfile) ;; # 开发用镜像,与生产镜像无关 deploy/docker/backend*.Dockerfile) backend=1 ;; deploy/docker/frontend*.Dockerfile) web=1 ;; deploy/caddy/*) web=1 ;; # Caddyfile 会被 COPY 进 web 镜像 # 进不了任何生产镜像的改动:文档、本地脚本、部署脚手架 docs/* | *.md | LICENSE) ;; .gitignore | .editorconfig | .prettierignore | .prettierrc.json) ;; .vscode/* | scripts/* | .githooks/*) ;; deploy/lib/* | deploy/*.sh) ;; # 其余(compose 文件、根目录杂项、未知路径)一律两端重建 *) backend=1; web=1 ;; esac done (( backend == 1 )) && out+=("backend") (( web == 1 )) && out+=("web") printf '%s\n' "${out[*]:-}" } case "${1:-}" in --git-diff) shift print_git_diff "$@" ;; *) classify_changed_paths ;; esac