#!/usr/bin/env bash set -euo pipefail ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)" ARTIFACT_ROOT="${RELEASE_GATE_ARTIFACT_DIR:-${ROOT_DIR}/artifacts/release-gate/$(date +%Y%m%d-%H%M%S)}" MODE="${1:-all}" CURRENT_STAGE="" mkdir -p "$ARTIFACT_ROOT" usage() { cat < 说明: static 运行部署契约、server Prisma 校验、client typecheck/build smoke-local 运行本地网页 smoke 门禁 full-local 运行本地全量门禁(server regression + client full E2E) project-wide 项目级门禁:static + 覆盖扫描 + UI/移动契约 + 迁移漂移诊断 + 业务流 + 全页面视觉 + live readiness deploy 调用 deploy/trialrun/deploy.sh,内部会串联 /ready、external deps、runtime smoke 与 live smoke all 依次执行 static -> smoke-local -> full-local -> deploy ci-release 依次执行 static -> full-local -> deploy r66-trialrun R66 小团队试点门禁:static -> server regression-all -> smoke-local 可选环境变量: RELEASE_GATE_ARTIFACT_DIR 失败产物输出目录 QA_* 透传给 scripts/qa/run-web-gates.sh LIVE_SMOKE_* 透传给 deploy.sh 与 live smoke PROJECT_WIDE_SKIP_BUSINESS_FLOW=1 跳过业务流 E2E PROJECT_WIDE_SKIP_VISUAL=1 跳过项目级视觉 E2E DEPLOY_SERVER / REMOTE_DIR / REMOTE_ENV_FILE / SYNC_ENV USAGE } collect_failure_artifacts() { local stage_name="${CURRENT_STAGE:-unknown}" local target_dir="${ARTIFACT_ROOT}/${stage_name}" mkdir -p "$target_dir" bash "$ROOT_DIR/scripts/qa/collect-failure-artifacts.sh" "$target_dir" >/dev/null 2>&1 || true echo "[release-gate] 已收集失败产物:$target_dir" >&2 } handle_error() { local exit_code="$?" echo "[release-gate][FAIL] 阶段 ${CURRENT_STAGE:-unknown} 失败(exit=${exit_code})" >&2 collect_failure_artifacts exit "$exit_code" } trap handle_error ERR run_stage() { local stage_name="$1" shift CURRENT_STAGE="$stage_name" echo ">>> [release-gate] ${stage_name}" "$@" } stage_static() { node "$ROOT_DIR/deploy/trialrun/validate-env-contract.js" ( cd "$ROOT_DIR/server" npm run prisma:validate ) ( cd "$ROOT_DIR/client" npm run typecheck npm run build ) } stage_smoke_local() { bash "$ROOT_DIR/scripts/qa/run-web-gates.sh" smoke-local } stage_full_local() { bash "$ROOT_DIR/scripts/qa/run-web-gates.sh" full-local } stage_server_regression_all() { ( cd "$ROOT_DIR/server" ./scripts/regression-all.sh ) } stage_project_wide_static() { local issue_csv="$ROOT_DIR/issues/2026-05-08_23-38-37-project-wide-qa-second-pass.csv" python3 /home/shunli/.codex/skills/plan/scripts/validate_issues_csv.py "$issue_csv" node "$ROOT_DIR/scripts/qa/check-ui-design-rules.mjs" --strict node "$ROOT_DIR/scripts/qa/check-mobile-route-contract.mjs" node "$ROOT_DIR/scripts/qa/check-flow-coverage.mjs" --json > "$ARTIFACT_ROOT/flow-coverage.json" node "$ROOT_DIR/scripts/qa/check-flow-coverage.mjs" --markdown "$ARTIFACT_ROOT/flow-coverage.md" node "$ROOT_DIR/scripts/qa/check-live-smoke-env.mjs" --json > "$ARTIFACT_ROOT/live-smoke-env.json" bash "$ROOT_DIR/scripts/qa/check-local-migration-drift.sh" } stage_project_wide_business_flow() { if [[ "${PROJECT_WIDE_SKIP_BUSINESS_FLOW:-0}" == "1" ]]; then echo "[release-gate] 跳过业务流 E2E(PROJECT_WIDE_SKIP_BUSINESS_FLOW=1)" return 0 fi bash "$ROOT_DIR/scripts/qa/run-business-flow-gates.sh" local } stage_project_wide_visual() { if [[ "${PROJECT_WIDE_SKIP_VISUAL:-0}" == "1" ]]; then echo "[release-gate] 跳过项目级视觉 E2E(PROJECT_WIDE_SKIP_VISUAL=1)" return 0 fi bash "$ROOT_DIR/scripts/qa/run-web-gates.sh" project-wide-visual-local } stage_project_wide_live_readiness() { LIVE_SMOKE_BASE_URL="${LIVE_SMOKE_BASE_URL:-https://oa.hyinda.com}" bash "$ROOT_DIR/scripts/qa/run-live-smoke-readiness.sh" } stage_deploy() { bash "$ROOT_DIR/deploy/trialrun/deploy.sh" } case "$MODE" in help|-h|--help) usage ;; static) run_stage static stage_static ;; smoke-local) run_stage smoke-local stage_smoke_local ;; full-local) run_stage full-local stage_full_local ;; project-wide) run_stage static stage_static run_stage project-wide-static stage_project_wide_static run_stage project-wide-business-flow stage_project_wide_business_flow run_stage project-wide-visual stage_project_wide_visual run_stage project-wide-live-readiness stage_project_wide_live_readiness ;; deploy) run_stage deploy stage_deploy ;; ci-release) run_stage static stage_static run_stage full-local stage_full_local run_stage deploy stage_deploy ;; r66-trialrun) run_stage static stage_static run_stage server-regression-all stage_server_regression_all run_stage smoke-local stage_smoke_local ;; all) run_stage static stage_static run_stage smoke-local stage_smoke_local run_stage full-local stage_full_local run_stage deploy stage_deploy ;; *) usage >&2 exit 1 ;; esac echo "[release-gate][PASS] 模式 ${MODE} 通过" echo "[release-gate] 产物目录:${ARTIFACT_ROOT}"