feat(release): add standard and fast 103 portal release modes
Formalize fast release as --mode fast so verified changes can skip canary promotion and full Gate reruns while keeping backups and health checks. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Executable
+6
@@ -0,0 +1,6 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
||||
|
||||
exec bash "${ROOT}/scripts/release-portal-runtime-prod.sh" --mode fast "$@"
|
||||
@@ -26,40 +26,97 @@ DRY_RUN=0
|
||||
SKIP_TESTS=0
|
||||
SKIP_BUILD=0
|
||||
AUTO_YES=0
|
||||
RELEASE_MODE="${MEMIND_RELEASE_MODE:-standard}"
|
||||
|
||||
cleanup() {
|
||||
rm -rf "${TMP_DIR}"
|
||||
}
|
||||
trap cleanup EXIT
|
||||
|
||||
say() {
|
||||
printf '\n[%s] %s\n' "$(date +%H:%M:%S)" "$*"
|
||||
}
|
||||
|
||||
usage() {
|
||||
cat <<'EOF'
|
||||
用法:
|
||||
bash scripts/release-portal-runtime-prod.sh [--dry-run] [--skip-tests] [--skip-build] [--yes]
|
||||
bash scripts/release-portal-runtime-prod.sh [--mode standard|fast] [--dry-run] [--skip-tests] [--skip-build] [--yes]
|
||||
bash scripts/release-portal-fast-prod.sh [--dry-run] [--skip-build] [--yes]
|
||||
|
||||
说明:
|
||||
1. 本地构建 Portal 无源码 runtime artifact
|
||||
2. 上传到 103(固定公网地址)
|
||||
3. 103 备份当前 Memind 全目录 + 持久目录
|
||||
4. 停止旧 Portal 服务
|
||||
5. 用 runtime artifact 替换 live 目录,只继承持久目录
|
||||
6. 启动 Portal,保持旧端口 8081
|
||||
7. 健康检查通过后重启 m.tkmind.cn 反向隧道 (105:19081 -> 103:8081)
|
||||
8. 旧源码目录移入 archive,不再保留可运行 live 源码
|
||||
发布模式:
|
||||
standard(默认)
|
||||
完整标准发布:灰度晋升证据 + Core+Impact Gate + 完整 MindSpace 守卫 + 103 整包替换。
|
||||
标准流程应先跑 scripts/release-portal-canary-prod.sh,验收后再执行本脚本。
|
||||
|
||||
fast
|
||||
快速发布:跳过灰度晋升证据、完整 Gate 重跑和 publish-guards:full。
|
||||
仍执行 check-release-ready、runtime 构建/校验、103 备份与回滚、goosed 预检、8081 健康检查。
|
||||
若当前 commit+artifact 已有有效 Gate report 则直接复用;否则只跑最小本地 smoke + release-gate 单测。
|
||||
快速发布需要明确人工批准(--yes 或发布前交互确认);全站用户会立即命中新版本。
|
||||
|
||||
环境变量:
|
||||
MEMIND_RELEASE_MODE=standard|fast
|
||||
ALLOW_DIRECT_STABLE_RELEASE=1
|
||||
仅在用户明确批准按 7/23 前规则直接整包时使用:允许 --skip-tests,
|
||||
并跳过 canary 晋升证据与 Core+Impact Gate report。
|
||||
已废弃;等价于 --mode fast。仅保留兼容旧命令。
|
||||
EOF
|
||||
}
|
||||
|
||||
is_fast_release() {
|
||||
[[ "${RELEASE_MODE}" == "fast" ]]
|
||||
}
|
||||
|
||||
run_fast_release_guards() {
|
||||
say "快速发布:运行最小本地验证"
|
||||
(
|
||||
cd "${ROOT}"
|
||||
npm test -- --test-name-pattern='publish|space|billing|wechat' >/dev/null
|
||||
npm run verify:mindspace-publish-guards >/dev/null
|
||||
npm run verify:page-data >/dev/null
|
||||
npm run test:release-gate:unit >/dev/null
|
||||
)
|
||||
}
|
||||
|
||||
verify_gate_for_release() {
|
||||
if is_fast_release; then
|
||||
if node "${ROOT}/scripts/verify-release-gate-report.mjs" --artifact "${RUNTIME_ROOT}" >/dev/null 2>&1; then
|
||||
say "快速发布:复用当前 commit 的有效 Gate report"
|
||||
node "${ROOT}/scripts/verify-release-gate-report.mjs" --artifact "${RUNTIME_ROOT}"
|
||||
return 0
|
||||
fi
|
||||
say "快速发布:无有效 Gate report,跳过 Core+Impact 重跑,改跑最小本地验证"
|
||||
run_fast_release_guards
|
||||
return 0
|
||||
fi
|
||||
|
||||
if ! node "${ROOT}/scripts/verify-release-gate-report.mjs" --artifact "${RUNTIME_ROOT}" >/dev/null 2>&1; then
|
||||
say "执行核心场景 + 变更影响域 Gate"
|
||||
DEPLOYED_SHA="${MEMIND_RELEASE_BASE_COMMIT:-}"
|
||||
if [[ -z "${DEPLOYED_SHA}" && "${DRY_RUN}" -ne 1 ]]; then
|
||||
DEPLOYED_SHA="$(
|
||||
ssh -o BatchMode=yes -o ConnectTimeout=15 "${HOST}" \
|
||||
"grep -E '^git_head=' '${APP_DIR}/.release-manifest.txt' 2>/dev/null | tail -1 | cut -d= -f2-" \
|
||||
2>/dev/null || true
|
||||
)"
|
||||
fi
|
||||
IMPACT_ARGS=(--artifact "${RUNTIME_ROOT}")
|
||||
if [[ -n "${DEPLOYED_SHA}" ]]; then
|
||||
IMPACT_ARGS+=(--deployed-commit "${DEPLOYED_SHA}")
|
||||
fi
|
||||
node "${ROOT}/scripts/run-release-gate-impact.mjs" "${IMPACT_ARGS[@]}"
|
||||
fi
|
||||
node "${ROOT}/scripts/verify-release-gate-report.mjs" --artifact "${RUNTIME_ROOT}"
|
||||
}
|
||||
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
--dry-run) DRY_RUN=1 ;;
|
||||
--skip-tests) SKIP_TESTS=1 ;;
|
||||
--skip-build) SKIP_BUILD=1 ;;
|
||||
--yes|-y) AUTO_YES=1 ;;
|
||||
--mode)
|
||||
RELEASE_MODE="${2:-}"
|
||||
shift 2
|
||||
;;
|
||||
--dry-run) DRY_RUN=1; shift ;;
|
||||
--skip-tests) SKIP_TESTS=1; shift ;;
|
||||
--skip-build) SKIP_BUILD=1; shift ;;
|
||||
--yes|-y) AUTO_YES=1; shift ;;
|
||||
-h|--help)
|
||||
usage
|
||||
exit 0
|
||||
@@ -70,18 +127,26 @@ while [[ $# -gt 0 ]]; do
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
shift
|
||||
done
|
||||
|
||||
say() {
|
||||
printf '\n[%s] %s\n' "$(date +%H:%M:%S)" "$*"
|
||||
}
|
||||
case "${RELEASE_MODE}" in
|
||||
standard|fast) ;;
|
||||
*)
|
||||
echo "未知发布模式: ${RELEASE_MODE}(允许 standard 或 fast)" >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
|
||||
if [[ "${ALLOW_DIRECT_STABLE_RELEASE:-0}" == "1" ]]; then
|
||||
say "ALLOW_DIRECT_STABLE_RELEASE 已废弃,按 --mode fast 处理"
|
||||
RELEASE_MODE=fast
|
||||
fi
|
||||
|
||||
if [[ "${SKIP_TESTS}" -eq 1 ]]; then
|
||||
if [[ "${ALLOW_DIRECT_STABLE_RELEASE:-0}" == "1" ]]; then
|
||||
say "允许 --skip-tests(ALLOW_DIRECT_STABLE_RELEASE=1,按 7/23 前直接整包规则;测试须已在发布前手动跑过)"
|
||||
if is_fast_release; then
|
||||
say "快速发布:跳过额外本地测试(发布前须已自行验证)"
|
||||
else
|
||||
echo "生产发布守门员禁止 --skip-tests。" >&2
|
||||
echo "标准发布禁止 --skip-tests。" >&2
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
@@ -97,8 +162,8 @@ if [[ "${ALLOW_MINDSPACE_PUBLIC_LINK_ISSUES:-0}" == "1" ]]; then
|
||||
fi
|
||||
|
||||
if [[ "${DRY_RUN}" -eq 0 ]]; then
|
||||
if [[ "${ALLOW_DIRECT_STABLE_RELEASE:-0}" == "1" ]]; then
|
||||
say "跳过灰度晋升证据(ALLOW_DIRECT_STABLE_RELEASE=1,按 7/23 前直接整包规则)"
|
||||
if is_fast_release; then
|
||||
say "快速发布:跳过 103 灰度晋升证据"
|
||||
else
|
||||
say "验证 103 灰度晋升证据"
|
||||
node "${ROOT}/scripts/verify-canary-promotion-evidence.mjs" --host "${HOST}"
|
||||
@@ -153,7 +218,9 @@ say "本地预检查"
|
||||
check_release_scope
|
||||
|
||||
if [[ "${SKIP_TESTS}" -ne 1 ]]; then
|
||||
if node "${ROOT}/scripts/resolve-release-ci-status.mjs" --commit "$(git -C "${ROOT}" rev-parse HEAD)"; then
|
||||
if is_fast_release; then
|
||||
say "快速发布:本地验证由 Gate 复用检查或 verify_gate_for_release 统一执行"
|
||||
elif node "${ROOT}/scripts/resolve-release-ci-status.mjs" --commit "$(git -C "${ROOT}" rev-parse HEAD)"; then
|
||||
say "Gitea CI already succeeded; skipping duplicate local npm test/verify"
|
||||
else
|
||||
say "运行最小验证"
|
||||
@@ -195,11 +262,15 @@ verify_runtime_artifact() {
|
||||
|
||||
verify_runtime_artifact
|
||||
|
||||
say "验证 MindSpace 发布与聊天 Finish 回归守卫"
|
||||
(
|
||||
cd "${ROOT}"
|
||||
npm run verify:mindspace-publish-guards:full
|
||||
)
|
||||
if is_fast_release; then
|
||||
say "快速发布:跳过 verify:mindspace-publish-guards:full"
|
||||
else
|
||||
say "验证 MindSpace 发布与聊天 Finish 回归守卫"
|
||||
(
|
||||
cd "${ROOT}"
|
||||
npm run verify:mindspace-publish-guards:full
|
||||
)
|
||||
fi
|
||||
|
||||
verify_mindspace_public_links() {
|
||||
local target_root="${1:-${ROOT}/MindSpace}"
|
||||
@@ -353,27 +424,7 @@ REMOTE
|
||||
}
|
||||
|
||||
say "验证与当前 main 和 runtime artifact 绑定的 Gate report"
|
||||
if [[ "${ALLOW_DIRECT_STABLE_RELEASE:-0}" == "1" ]]; then
|
||||
say "跳过 Core+Impact Gate report(ALLOW_DIRECT_STABLE_RELEASE=1,按 7/23 前直接整包规则)"
|
||||
elif ! node "${ROOT}/scripts/verify-release-gate-report.mjs" --artifact "${RUNTIME_ROOT}" >/dev/null 2>&1; then
|
||||
say "执行核心场景 + 变更影响域 Gate"
|
||||
DEPLOYED_SHA="${MEMIND_RELEASE_BASE_COMMIT:-}"
|
||||
if [[ -z "${DEPLOYED_SHA}" && "${DRY_RUN}" -ne 1 ]]; then
|
||||
DEPLOYED_SHA="$(
|
||||
ssh -o BatchMode=yes -o ConnectTimeout=15 "${HOST}" \
|
||||
"grep -E '^git_head=' '${APP_DIR}/.release-manifest.txt' 2>/dev/null | tail -1 | cut -d= -f2-" \
|
||||
2>/dev/null || true
|
||||
)"
|
||||
fi
|
||||
IMPACT_ARGS=(--artifact "${RUNTIME_ROOT}")
|
||||
if [[ -n "${DEPLOYED_SHA}" ]]; then
|
||||
IMPACT_ARGS+=(--deployed-commit "${DEPLOYED_SHA}")
|
||||
fi
|
||||
node "${ROOT}/scripts/run-release-gate-impact.mjs" "${IMPACT_ARGS[@]}"
|
||||
node "${ROOT}/scripts/verify-release-gate-report.mjs" --artifact "${RUNTIME_ROOT}"
|
||||
else
|
||||
node "${ROOT}/scripts/verify-release-gate-report.mjs" --artifact "${RUNTIME_ROOT}"
|
||||
fi
|
||||
verify_gate_for_release
|
||||
|
||||
if [[ "${DRY_RUN}" -ne 1 ]]; then
|
||||
say "执行 103 只读预检"
|
||||
@@ -389,6 +440,11 @@ if [[ "${AUTO_YES}" -ne 1 && "${DRY_RUN}" -ne 1 ]]; then
|
||||
echo "发布编号: ${RELEASE_ID}"
|
||||
echo "本地 HEAD: $(git -C "${ROOT}" rev-parse HEAD 2>/dev/null || echo unknown)"
|
||||
echo "说明: 此次只切换 test-memind Portal 到无源码 runtime,不包含 memindadm / memindplaza"
|
||||
if is_fast_release; then
|
||||
echo "模式: 快速发布(跳过灰度晋升证据与完整 Gate 重跑)"
|
||||
else
|
||||
echo "模式: 标准发布"
|
||||
fi
|
||||
read -r -p "确认继续发布到 103? [y/N] " confirm </dev/tty
|
||||
[[ "${confirm}" =~ ^[Yy]$ ]] || exit 0
|
||||
fi
|
||||
@@ -400,6 +456,7 @@ say "生成发布清单"
|
||||
echo "host=$(hostname)"
|
||||
echo "git_head=$(git -C "${ROOT}" rev-parse HEAD 2>/dev/null || echo unknown)"
|
||||
echo "git_branch=$(git -C "${ROOT}" branch --show-current 2>/dev/null || echo detached)"
|
||||
echo "release_mode=${RELEASE_MODE}"
|
||||
echo "artifact=.runtime/portal"
|
||||
echo "persisted_items=.env, MindSpace, data, users, .tailscale, public/plaza-covers, logs"
|
||||
} > "${MANIFEST_PATH}"
|
||||
|
||||
Reference in New Issue
Block a user