diff --git a/apps/backend/src/deploy-change-detection.test.ts b/apps/backend/src/deploy-change-detection.test.ts index ddb12285..b54515d6 100644 --- a/apps/backend/src/deploy-change-detection.test.ts +++ b/apps/backend/src/deploy-change-detection.test.ts @@ -132,5 +132,12 @@ test( assert.ok(source.includes('DIFF_KNOWN'), '缺少"改动范围是否已知"的判定') assert.ok(source.includes('migrations_changed'), '缺少 migrations 改动判定') assert.ok(source.includes('--backup 可强制'), '缺少强制备份的提示') + + // --backend-only/--all 这类参数只能覆盖"重建哪一端",不能顺手把"改了什么"一起跳过: + // 否则 DIFF_KNOWN 永远是 false,无 schema 变更也会去跑 pg_dump + 传 OSS(生产 ~12 分钟)。 + const computeIndex = source.indexOf('compute_changed_files\n') + const detectIndex = source.indexOf('detect_changed_services\n') + assert.ok(computeIndex > 0 && detectIndex > 0, '找不到改动计算/重建判定的调用') + assert.ok(computeIndex < detectIndex, '必须先算改动范围,再决定重建范围') }, ) diff --git a/deploy/ubuntu-deploy.sh b/deploy/ubuntu-deploy.sh index a260b25d..add74304 100755 --- a/deploy/ubuntu-deploy.sh +++ b/deploy/ubuntu-deploy.sh @@ -32,6 +32,8 @@ BUILD_WEB=false CHANGED_FILES="" CHANGE_REASON="" DIFF_KNOWN=false +ENV_CHANGED=false +LAST_DEPLOYED_SHA="" DEPLOY_STARTED_AT=${SECONDS} TIMING_LABELS=() TIMING_SECONDS=() @@ -205,64 +207,37 @@ write_deploy_state() { log "已记录部署状态:${DEPLOY_STATE_FILE}(commit=${head_sha:0:12})" } -# 决定这次要重建哪一端:--all/--backend-only/--web-only > .deploy-state + git diff > 保守兜底。 -detect_changed_services() { - local head_sha last_sha env_hash last_env_hash services +# 只回答"自上次成功部署以来改了什么",不决定重建范围。 +# 拆成两步的原因:--backend-only/--all 这类显式参数只该覆盖"重建哪一端", +# 不该把"改动范围未知"一起带进来——否则会连"无迁移改动就跳过备份"的判断也一起退化成"必须备份"。 +compute_changed_files() { + local head_sha last_sha env_hash last_env_hash - BUILD_BACKEND=false - BUILD_WEB=false CHANGED_FILES="" CHANGE_REASON="" DIFF_KNOWN=false - - if [[ "${FORCE_BACKEND}" == "true" || "${FORCE_WEB}" == "true" ]]; then - [[ "${FORCE_BACKEND}" == "true" ]] && BUILD_BACKEND=true - [[ "${FORCE_WEB}" == "true" ]] && BUILD_WEB=true - CHANGE_REASON="命令行指定了 --backend-only/--web-only" - return - fi - - if [[ "${FORCE_ALL}" == "true" ]]; then - BUILD_BACKEND=true - BUILD_WEB=true - CHANGE_REASON="命令行指定了 --all(强制全量重建)" - return - fi - - if [[ "${RESET_DB}" == "true" ]]; then - BUILD_BACKEND=true - BUILD_WEB=true - CHANGE_REASON="--reset-db 需要整套服务重建" - return - fi + ENV_CHANGED=false + LAST_DEPLOYED_SHA="" head_sha="$(git -C "${PROJECT_ROOT}" rev-parse HEAD 2>/dev/null || true)" last_sha="$(read_state_value commit)" if [[ -z "${head_sha}" ]]; then - BUILD_BACKEND=true - BUILD_WEB=true CHANGE_REASON="项目不是 git 仓库或读不到 HEAD,无法判断改动" return fi if [[ -z "${last_sha}" ]]; then - BUILD_BACKEND=true - BUILD_WEB=true CHANGE_REASON="没有 ${DEPLOY_STATE_FILE##*/} 记录(首次使用按需重建),先全量重建一次" return fi if ! git -C "${PROJECT_ROOT}" cat-file -e "${last_sha}^{commit}" 2>/dev/null; then - BUILD_BACKEND=true - BUILD_WEB=true CHANGE_REASON="上次部署的提交 ${last_sha:0:12} 已不在仓库中(force push?),全量重建" return fi if ! git -C "${PROJECT_ROOT}" merge-base --is-ancestor "${last_sha}" "${head_sha}" 2>/dev/null; then - BUILD_BACKEND=true - BUILD_WEB=true CHANGE_REASON="上次部署的提交 ${last_sha:0:12} 不是 HEAD 的祖先(回滚/分支切换?),全量重建" return fi @@ -271,13 +246,55 @@ detect_changed_services() { # 那里关掉了 core.quotePath,中文路径(docs/xxx.md)不会被转义成 "\351..." 而匹配不上规则。 CHANGED_FILES="$(bash "${PROJECT_ROOT}/deploy/lib/classify-changes.sh" --git-diff "${last_sha}" "${head_sha}" 2>/dev/null || true)" DIFF_KNOWN=true + LAST_DEPLOYED_SHA="${last_sha}" env_hash="$(file_sha256 "${ENV_FILE}")" last_env_hash="$(read_state_value env_sha256)" if [[ -n "${env_hash}" && -n "${last_env_hash}" && "${env_hash}" != "${last_env_hash}" ]]; then + ENV_CHANGED=true + CHANGE_REASON=".env 与上次部署时不一致(运行时环境/构建参数可能变化)" + return + fi + + if [[ -z "${CHANGED_FILES}" ]]; then + CHANGE_REASON="自 ${last_sha:0:12} 以来没有代码改动" + else + CHANGE_REASON="自 ${last_sha:0:12} 以来改动 $(printf '%s\n' "${CHANGED_FILES}" | grep -c .) 个文件" + fi +} + +# 只决定"重建哪一端":显式参数 > 改动范围 > 保守兜底。 +detect_changed_services() { + local services + + BUILD_BACKEND=false + BUILD_WEB=false + + if [[ "${FORCE_BACKEND}" == "true" || "${FORCE_WEB}" == "true" ]]; then + [[ "${FORCE_BACKEND}" == "true" ]] && BUILD_BACKEND=true + [[ "${FORCE_WEB}" == "true" ]] && BUILD_WEB=true + CHANGE_REASON="${CHANGE_REASON};命令行指定了 --backend-only/--web-only" + return + fi + + if [[ "${FORCE_ALL}" == "true" ]]; then + BUILD_BACKEND=true + BUILD_WEB=true + CHANGE_REASON="${CHANGE_REASON};命令行指定了 --all" + return + fi + + if [[ "${RESET_DB}" == "true" ]]; then + BUILD_BACKEND=true + BUILD_WEB=true + CHANGE_REASON="${CHANGE_REASON};--reset-db 需要整套服务重建" + return + fi + + # 算不出改动范围、或 .env 变了:两端都重建(保守兜底) + if [[ "${DIFF_KNOWN}" != "true" || "${ENV_CHANGED}" == "true" ]]; then BUILD_BACKEND=true BUILD_WEB=true - CHANGE_REASON=".env 与上次部署时不一致(运行时环境/构建参数可能变化),两端全量重建" return fi @@ -288,12 +305,6 @@ detect_changed_services() { if [[ " ${services} " == *" web "* ]]; then BUILD_WEB=true fi - - if [[ -z "${CHANGED_FILES}" ]]; then - CHANGE_REASON="自 ${last_sha:0:12} 以来没有代码改动" - else - CHANGE_REASON="自 ${last_sha:0:12} 以来改动 $(printf '%s\n' "${CHANGED_FILES}" | grep -c . ) 个文件" - fi } migrations_changed() { @@ -302,12 +313,11 @@ migrations_changed() { } print_plan() { - local head_sha last_sha targets="(无)" skipped + local head_sha targets="(无)" skipped head_sha="$(git -C "${PROJECT_ROOT}" rev-parse HEAD 2>/dev/null || true)" - last_sha="$(read_state_value commit)" log "部署计划:" - printf ' 上次成功部署:%s\n' "${last_sha:-(无记录)}" + printf ' 上次成功部署:%s\n' "${LAST_DEPLOYED_SHA:-(无记录)}" printf ' 本次 HEAD :%s\n' "${head_sha:-(非 git 仓库)}" printf ' 改动依据 :%s\n' "${CHANGE_REASON:-未判断}" @@ -828,6 +838,7 @@ main() { ensure_data_dir ensure_disk_space + compute_changed_files detect_changed_services print_plan