fix(release): support scoped backend preflight
This commit is contained in:
+137
-98
@@ -43,34 +43,50 @@ normalized_jar_content_sha256() {
|
||||
rm -f "$manifest_path"
|
||||
}
|
||||
|
||||
verify_remote_match="${RELEASE_VERIFY_REMOTE_MATCH:-false}"
|
||||
verify_remote_backend="${RELEASE_VERIFY_REMOTE_BACKEND:-false}"
|
||||
verify_remote_schema="${RELEASE_VERIFY_REMOTE_SCHEMA:-false}"
|
||||
remote_verification_requested="false"
|
||||
if [[ "$verify_remote_match" == "true" || "$verify_remote_backend" == "true" || "$verify_remote_schema" == "true" ]]; then
|
||||
remote_verification_requested="true"
|
||||
fi
|
||||
if [[ "$remote_verification_requested" == "true" && -z "${RELEASE_REMOTE_URL:-}" ]]; then
|
||||
fail "remote verification flags require RELEASE_REMOTE_URL"
|
||||
fi
|
||||
|
||||
frontend_index="frontend/dist/index.html"
|
||||
mobile_index="mobile-uni/dist/build/h5/index.html"
|
||||
backend_jar="backend/ruoyi-admin/target/ruoyi-admin.jar"
|
||||
require_file "$frontend_index"
|
||||
require_file "$mobile_index"
|
||||
require_file "$backend_jar"
|
||||
backend_module_jar_name="$(unzip -Z1 "$backend_jar" | sed -nE 's#BOOT-INF/lib/(ruoyi-aihr-[^/]+\.jar)#\1#p' | head -1)"
|
||||
[[ -n "$backend_module_jar_name" ]] || fail "backend jar does not contain the ruoyi-aihr module"
|
||||
|
||||
frontend_asset="$(sed -nE 's/.*src="([^"]+\.js)".*/\1/p' "$frontend_index" | head -1)"
|
||||
mobile_asset="$(sed -nE 's/.*src="([^"]+\.js)".*/\1/p' "$mobile_index" | head -1)"
|
||||
[[ -n "$frontend_asset" ]] || fail "frontend index does not reference a JavaScript entry"
|
||||
[[ -n "$mobile_asset" ]] || fail "mobile H5 index does not reference a JavaScript entry"
|
||||
|
||||
frontend_asset_path="frontend/dist/${frontend_asset#/}"
|
||||
mobile_asset_rel="${mobile_asset#/}"
|
||||
mobile_asset_rel="${mobile_asset_rel#h5/}"
|
||||
mobile_asset_path="mobile-uni/dist/build/h5/$mobile_asset_rel"
|
||||
require_file "$frontend_asset_path"
|
||||
require_file "$mobile_asset_path"
|
||||
grep -q '/h5/' "$mobile_index" || fail "mobile H5 index does not contain the /h5/ base path"
|
||||
|
||||
if [[ "${RELEASE_VERIFY_REMOTE_BACKEND:-false}" == "true" && "${RELEASE_VERIFY_REMOTE_MATCH:-false}" != "true" ]]; then
|
||||
fail "RELEASE_VERIFY_REMOTE_BACKEND=true requires RELEASE_VERIFY_REMOTE_MATCH=true"
|
||||
require_static_artifacts="true"
|
||||
require_backend_artifact="true"
|
||||
if [[ "$remote_verification_requested" == "true" ]]; then
|
||||
require_static_artifacts="$verify_remote_match"
|
||||
require_backend_artifact="$verify_remote_backend"
|
||||
fi
|
||||
if [[ "${RELEASE_VERIFY_REMOTE_SCHEMA:-false}" == "true" && "${RELEASE_VERIFY_REMOTE_MATCH:-false}" != "true" ]]; then
|
||||
fail "RELEASE_VERIFY_REMOTE_SCHEMA=true requires RELEASE_VERIFY_REMOTE_MATCH=true"
|
||||
|
||||
if [[ "$require_static_artifacts" == "true" ]]; then
|
||||
require_file "$frontend_index"
|
||||
require_file "$mobile_index"
|
||||
frontend_asset="$(sed -nE 's/.*src="([^"]+\.js)".*/\1/p' "$frontend_index" | head -1)"
|
||||
mobile_asset="$(sed -nE 's/.*src="([^"]+\.js)".*/\1/p' "$mobile_index" | head -1)"
|
||||
[[ -n "$frontend_asset" ]] || fail "frontend index does not reference a JavaScript entry"
|
||||
[[ -n "$mobile_asset" ]] || fail "mobile H5 index does not reference a JavaScript entry"
|
||||
|
||||
frontend_asset_path="frontend/dist/${frontend_asset#/}"
|
||||
mobile_asset_rel="${mobile_asset#/}"
|
||||
mobile_asset_rel="${mobile_asset_rel#h5/}"
|
||||
mobile_asset_path="mobile-uni/dist/build/h5/$mobile_asset_rel"
|
||||
require_file "$frontend_asset_path"
|
||||
require_file "$mobile_asset_path"
|
||||
grep -q '/h5/' "$mobile_index" || fail "mobile H5 index does not contain the /h5/ base path"
|
||||
fi
|
||||
|
||||
if [[ "$require_backend_artifact" == "true" ]]; then
|
||||
require_file "$backend_jar"
|
||||
backend_module_jar_name="$(unzip -Z1 "$backend_jar" | sed -nE 's#BOOT-INF/lib/(ruoyi-aihr-[^/]+\.jar)#\1#p' | head -1)"
|
||||
[[ -n "$backend_module_jar_name" ]] || fail "backend jar does not contain the ruoyi-aihr module"
|
||||
fi
|
||||
|
||||
if [[ "${AIHR_PRACTICE_RUNTIME_SCHEMA_BOOTSTRAP:-false}" == "true" ]]; then
|
||||
fail "AIHR_PRACTICE_RUNTIME_SCHEMA_BOOTSTRAP must stay false for a release; apply the formal SQL migration instead"
|
||||
fi
|
||||
@@ -92,11 +108,15 @@ require_fresh_artifact() {
|
||||
[[ "$artifact_epoch" -ge "$head_epoch" ]] || fail "artifact is older than HEAD; rebuild before release: $artifact"
|
||||
}
|
||||
|
||||
require_fresh_artifact "$frontend_index"
|
||||
require_fresh_artifact "$frontend_asset_path"
|
||||
require_fresh_artifact "$mobile_index"
|
||||
require_fresh_artifact "$mobile_asset_path"
|
||||
require_fresh_artifact "$backend_jar"
|
||||
if [[ "$require_static_artifacts" == "true" ]]; then
|
||||
require_fresh_artifact "$frontend_index"
|
||||
require_fresh_artifact "$frontend_asset_path"
|
||||
require_fresh_artifact "$mobile_index"
|
||||
require_fresh_artifact "$mobile_asset_path"
|
||||
fi
|
||||
if [[ "$require_backend_artifact" == "true" ]]; then
|
||||
require_fresh_artifact "$backend_jar"
|
||||
fi
|
||||
|
||||
require_remote_business_success() {
|
||||
local label="$1"
|
||||
@@ -759,67 +779,30 @@ REMOTE
|
||||
echo "remote_personal_oss_configuration=true $remote_ssh:$remote_db"
|
||||
}
|
||||
|
||||
changed_files="$(git status --porcelain)"
|
||||
[[ -z "$changed_files" ]] || fail "worktree has uncommitted changes; commit the release batch before publishing"
|
||||
require_remote_backend_match() {
|
||||
command -v ssh >/dev/null 2>&1 || fail "ssh is required for remote backend verification"
|
||||
local remote_ssh="${RELEASE_REMOTE_SSH:-YCWY}"
|
||||
local remote_backend_path="${RELEASE_REMOTE_BACKEND_PATH:-/opt/wygj/app/ruoyi-admin.jar}"
|
||||
[[ "$remote_backend_path" =~ ^/[A-Za-z0-9._/-]+$ ]] || fail "remote backend path must be an absolute safe path: $remote_backend_path"
|
||||
|
||||
echo "commit=$(git rev-parse HEAD)"
|
||||
echo "head_epoch=$head_epoch"
|
||||
echo "worktree=clean"
|
||||
echo "frontend_index_sha256=$(sha256 "$frontend_index")"
|
||||
echo "frontend_asset=$frontend_asset"
|
||||
echo "frontend_asset_sha256=$(sha256 "$frontend_asset_path")"
|
||||
echo "mobile_index_sha256=$(sha256 "$mobile_index")"
|
||||
echo "mobile_asset=$mobile_asset"
|
||||
echo "mobile_asset_sha256=$(sha256 "$mobile_asset_path")"
|
||||
echo "backend_jar_sha256=$(sha256 "$backend_jar")"
|
||||
local remote_backend_jar_sha256
|
||||
local local_backend_jar_sha256
|
||||
local local_backend_module_jar
|
||||
local local_backend_module_sha256
|
||||
local remote_backend_module_sha_file
|
||||
local remote_backend_module_sha256
|
||||
|
||||
if [[ -n "${RELEASE_REMOTE_URL:-}" ]]; then
|
||||
remote="${RELEASE_REMOTE_URL%/}"
|
||||
curl -fsS --max-time 15 "$remote/" >/dev/null || fail "remote root check failed: $remote/"
|
||||
echo "remote_root=200 $remote/"
|
||||
require_remote_business_success "remote_tenant_list" "$remote/prod-api/auth/tenant/list"
|
||||
require_remote_business_success "remote_mobile_home" "$remote/prod-api/api/aihr/mobile/home/user"
|
||||
remote_backend_jar_sha256="$(ssh -o BatchMode=yes -o ConnectTimeout=10 "$remote_ssh" "sha256sum '$remote_backend_path'" | awk '{print $1}')" \
|
||||
|| fail "remote backend hash check failed: $remote_ssh:$remote_backend_path"
|
||||
[[ "$remote_backend_jar_sha256" =~ ^[0-9a-f]{64}$ ]] || fail "remote backend hash is invalid: $remote_ssh:$remote_backend_path"
|
||||
local_backend_jar_sha256="$(sha256 "$backend_jar")"
|
||||
local_backend_module_jar="$(mktemp)"
|
||||
unzip -p "$backend_jar" "BOOT-INF/lib/$backend_module_jar_name" > "$local_backend_module_jar"
|
||||
local_backend_module_sha256="$(normalized_jar_content_sha256 "$local_backend_module_jar")"
|
||||
rm -f "$local_backend_module_jar"
|
||||
|
||||
if [[ "${RELEASE_VERIFY_REMOTE_SCHEMA:-false}" == "true" ]]; then
|
||||
require_remote_schema
|
||||
fi
|
||||
|
||||
if [[ "${RELEASE_VERIFY_REMOTE_SCHEMA:-false}" == "true" || "${RELEASE_VERIFY_REMOTE_BACKEND:-false}" == "true" ]]; then
|
||||
require_remote_practice_schema_guard
|
||||
fi
|
||||
|
||||
if [[ "${RELEASE_VERIFY_REMOTE_MATCH:-false}" == "true" ]]; then
|
||||
remote_frontend_asset="$(curl -fsS --max-time 15 "$remote/" | sed -nE 's/.*src="(\/assets\/[^" ]+\.js)".*/\1/p' | head -n 1)"
|
||||
remote_mobile_asset="$(curl -fsS --max-time 15 "$remote/h5/" | sed -nE 's/.*src="(\/h5\/assets\/[^" ]+\.js)".*/\1/p' | head -n 1)"
|
||||
[[ "$remote_frontend_asset" == /assets/*.js ]] || fail "remote frontend entry asset not found"
|
||||
[[ "$remote_mobile_asset" == /h5/assets/*.js ]] || fail "remote mobile H5 entry asset not found"
|
||||
|
||||
remote_frontend_sha256="$(curl -fsS --max-time 15 "$remote$remote_frontend_asset" | sha256_stream)"
|
||||
remote_mobile_sha256="$(curl -fsS --max-time 15 "$remote$remote_mobile_asset" | sha256_stream)"
|
||||
local_frontend_sha256="$(sha256 "$frontend_asset_path")"
|
||||
local_mobile_sha256="$(sha256 "$mobile_asset_path")"
|
||||
echo "remote_frontend_asset=$remote_frontend_asset"
|
||||
echo "remote_frontend_asset_sha256=$remote_frontend_sha256"
|
||||
echo "remote_mobile_asset=$remote_mobile_asset"
|
||||
echo "remote_mobile_asset_sha256=$remote_mobile_sha256"
|
||||
[[ "$remote_frontend_sha256" == "$local_frontend_sha256" ]] || fail "remote frontend asset does not match local build"
|
||||
[[ "$remote_mobile_sha256" == "$local_mobile_sha256" ]] || fail "remote mobile H5 asset does not match local build"
|
||||
echo "remote_asset_match=true"
|
||||
|
||||
if [[ "${RELEASE_VERIFY_REMOTE_BACKEND:-false}" == "true" ]]; then
|
||||
command -v ssh >/dev/null 2>&1 || fail "ssh is required for remote backend verification"
|
||||
remote_ssh="${RELEASE_REMOTE_SSH:-YCWY}"
|
||||
remote_backend_path="${RELEASE_REMOTE_BACKEND_PATH:-/opt/wygj/app/ruoyi-admin.jar}"
|
||||
[[ "$remote_backend_path" =~ ^/[A-Za-z0-9._/-]+$ ]] || fail "remote backend path must be an absolute safe path: $remote_backend_path"
|
||||
remote_backend_jar_sha256="$(ssh -o BatchMode=yes -o ConnectTimeout=10 "$remote_ssh" "sha256sum '$remote_backend_path'" | awk '{print $1}')" || fail "remote backend hash check failed: $remote_ssh:$remote_backend_path"
|
||||
[[ "$remote_backend_jar_sha256" =~ ^[0-9a-f]{64}$ ]] || fail "remote backend hash is invalid: $remote_ssh:$remote_backend_path"
|
||||
local_backend_jar_sha256="$(sha256 "$backend_jar")"
|
||||
local_backend_module_jar="$(mktemp)"
|
||||
unzip -p "$backend_jar" "BOOT-INF/lib/$backend_module_jar_name" > "$local_backend_module_jar"
|
||||
local_backend_module_sha256="$(normalized_jar_content_sha256 "$local_backend_module_jar")"
|
||||
rm -f "$local_backend_module_jar"
|
||||
remote_backend_module_sha_file="$(mktemp)"
|
||||
ssh -o BatchMode=yes -o ConnectTimeout=10 "$remote_ssh" bash -s -- "$remote_backend_path" "$backend_module_jar_name" > "$remote_backend_module_sha_file" <<'REMOTE'
|
||||
remote_backend_module_sha_file="$(mktemp)"
|
||||
ssh -o BatchMode=yes -o ConnectTimeout=10 "$remote_ssh" bash -s -- "$remote_backend_path" "$backend_module_jar_name" > "$remote_backend_module_sha_file" <<'REMOTE'
|
||||
set -euo pipefail
|
||||
outer="$1"
|
||||
module="$2"
|
||||
@@ -838,18 +821,74 @@ while IFS= read -r entry; do
|
||||
done < <(unzip -Z1 "$nested" | LC_ALL=C sort)
|
||||
sha256sum "$manifest" | awk '{print $1}'
|
||||
REMOTE
|
||||
remote_backend_module_sha256="$(sed -n '1p' "$remote_backend_module_sha_file")"
|
||||
rm -f "$remote_backend_module_sha_file"
|
||||
[[ -n "$remote_backend_module_sha256" ]] || fail "remote backend module hash check returned no value: $remote_ssh:$remote_backend_path"
|
||||
[[ "$remote_backend_module_sha256" =~ ^[0-9a-f]{64}$ ]] || fail "remote backend module hash is invalid: $remote_ssh:$remote_backend_path"
|
||||
echo "remote_backend_jar_sha256=$remote_backend_jar_sha256"
|
||||
echo "local_backend_jar_sha256=$local_backend_jar_sha256"
|
||||
echo "backend_module=$backend_module_jar_name"
|
||||
echo "remote_backend_module_sha256=$remote_backend_module_sha256"
|
||||
echo "local_backend_module_sha256=$local_backend_module_sha256"
|
||||
[[ "$remote_backend_module_sha256" == "$local_backend_module_sha256" ]] || fail "remote AIHR module does not match local build"
|
||||
echo "remote_backend_module_match=true"
|
||||
fi
|
||||
remote_backend_module_sha256="$(sed -n '1p' "$remote_backend_module_sha_file")"
|
||||
rm -f "$remote_backend_module_sha_file"
|
||||
[[ -n "$remote_backend_module_sha256" ]] || fail "remote backend module hash check returned no value: $remote_ssh:$remote_backend_path"
|
||||
[[ "$remote_backend_module_sha256" =~ ^[0-9a-f]{64}$ ]] || fail "remote backend module hash is invalid: $remote_ssh:$remote_backend_path"
|
||||
|
||||
echo "remote_backend_jar_sha256=$remote_backend_jar_sha256"
|
||||
echo "local_backend_jar_sha256=$local_backend_jar_sha256"
|
||||
echo "backend_module=$backend_module_jar_name"
|
||||
echo "remote_backend_module_sha256=$remote_backend_module_sha256"
|
||||
echo "local_backend_module_sha256=$local_backend_module_sha256"
|
||||
[[ "$remote_backend_module_sha256" == "$local_backend_module_sha256" ]] || fail "remote AIHR module does not match local build"
|
||||
echo "remote_backend_module_match=true"
|
||||
}
|
||||
|
||||
changed_files="$(git status --porcelain)"
|
||||
[[ -z "$changed_files" ]] || fail "worktree has uncommitted changes; commit the release batch before publishing"
|
||||
|
||||
echo "commit=$(git rev-parse HEAD)"
|
||||
echo "head_epoch=$head_epoch"
|
||||
echo "worktree=clean"
|
||||
if [[ "$require_static_artifacts" == "true" ]]; then
|
||||
echo "frontend_index_sha256=$(sha256 "$frontend_index")"
|
||||
echo "frontend_asset=$frontend_asset"
|
||||
echo "frontend_asset_sha256=$(sha256 "$frontend_asset_path")"
|
||||
echo "mobile_index_sha256=$(sha256 "$mobile_index")"
|
||||
echo "mobile_asset=$mobile_asset"
|
||||
echo "mobile_asset_sha256=$(sha256 "$mobile_asset_path")"
|
||||
fi
|
||||
if [[ "$require_backend_artifact" == "true" ]]; then
|
||||
echo "backend_jar_sha256=$(sha256 "$backend_jar")"
|
||||
fi
|
||||
|
||||
if [[ -n "${RELEASE_REMOTE_URL:-}" ]]; then
|
||||
remote="${RELEASE_REMOTE_URL%/}"
|
||||
curl -fsS --max-time 15 "$remote/" >/dev/null || fail "remote root check failed: $remote/"
|
||||
echo "remote_root=200 $remote/"
|
||||
require_remote_business_success "remote_tenant_list" "$remote/prod-api/auth/tenant/list"
|
||||
require_remote_business_success "remote_mobile_home" "$remote/prod-api/api/aihr/mobile/home/user"
|
||||
|
||||
if [[ "$verify_remote_schema" == "true" ]]; then
|
||||
require_remote_schema
|
||||
fi
|
||||
|
||||
if [[ "$verify_remote_schema" == "true" || "$verify_remote_backend" == "true" ]]; then
|
||||
require_remote_practice_schema_guard
|
||||
fi
|
||||
|
||||
if [[ "$verify_remote_backend" == "true" ]]; then
|
||||
require_remote_backend_match
|
||||
fi
|
||||
|
||||
if [[ "$verify_remote_match" == "true" ]]; then
|
||||
remote_frontend_asset="$(curl -fsS --max-time 15 "$remote/" | sed -nE 's/.*src="(\/assets\/[^" ]+\.js)".*/\1/p' | head -n 1)"
|
||||
remote_mobile_asset="$(curl -fsS --max-time 15 "$remote/h5/" | sed -nE 's/.*src="(\/h5\/assets\/[^" ]+\.js)".*/\1/p' | head -n 1)"
|
||||
[[ "$remote_frontend_asset" == /assets/*.js ]] || fail "remote frontend entry asset not found"
|
||||
[[ "$remote_mobile_asset" == /h5/assets/*.js ]] || fail "remote mobile H5 entry asset not found"
|
||||
|
||||
remote_frontend_sha256="$(curl -fsS --max-time 15 "$remote$remote_frontend_asset" | sha256_stream)"
|
||||
remote_mobile_sha256="$(curl -fsS --max-time 15 "$remote$remote_mobile_asset" | sha256_stream)"
|
||||
local_frontend_sha256="$(sha256 "$frontend_asset_path")"
|
||||
local_mobile_sha256="$(sha256 "$mobile_asset_path")"
|
||||
echo "remote_frontend_asset=$remote_frontend_asset"
|
||||
echo "remote_frontend_asset_sha256=$remote_frontend_sha256"
|
||||
echo "remote_mobile_asset=$remote_mobile_asset"
|
||||
echo "remote_mobile_asset_sha256=$remote_mobile_sha256"
|
||||
[[ "$remote_frontend_sha256" == "$local_frontend_sha256" ]] || fail "remote frontend asset does not match local build"
|
||||
[[ "$remote_mobile_sha256" == "$local_mobile_sha256" ]] || fail "remote mobile H5 asset does not match local build"
|
||||
echo "remote_asset_match=true"
|
||||
fi
|
||||
fi
|
||||
|
||||
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
|
||||
SCRIPT="$ROOT_DIR/scripts/release-preflight.sh"
|
||||
DEV_SETUP="$ROOT_DIR/docs/DEV_SETUP.md"
|
||||
|
||||
bash -n "$SCRIPT"
|
||||
|
||||
backend_without_url="$(
|
||||
RELEASE_VERIFY_REMOTE_BACKEND=true bash "$SCRIPT" 2>&1 || true
|
||||
)"
|
||||
grep -Fq 'remote verification flags require RELEASE_REMOTE_URL' <<<"$backend_without_url"
|
||||
|
||||
schema_without_url="$(
|
||||
RELEASE_VERIFY_REMOTE_SCHEMA=true bash "$SCRIPT" 2>&1 || true
|
||||
)"
|
||||
grep -Fq 'remote verification flags require RELEASE_REMOTE_URL' <<<"$schema_without_url"
|
||||
|
||||
if grep -Fq 'RELEASE_VERIFY_REMOTE_BACKEND=true requires RELEASE_VERIFY_REMOTE_MATCH=true' "$SCRIPT"; then
|
||||
echo 'FAIL: backend-only verification is still coupled to static resource matching' >&2
|
||||
exit 1
|
||||
fi
|
||||
if grep -Fq 'RELEASE_VERIFY_REMOTE_SCHEMA=true requires RELEASE_VERIFY_REMOTE_MATCH=true' "$SCRIPT"; then
|
||||
echo 'FAIL: schema-only verification is still coupled to static resource matching' >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
backend_call_line="$(grep -n '^[[:space:]]*require_remote_backend_match$' "$SCRIPT" | cut -d: -f1)"
|
||||
static_scope_line="$(grep -n '^[[:space:]]*if \[\[ "\$verify_remote_match" == "true" \]\]; then$' "$SCRIPT" | cut -d: -f1)"
|
||||
[[ -n "$backend_call_line" && -n "$static_scope_line" && "$backend_call_line" -lt "$static_scope_line" ]]
|
||||
|
||||
grep -Fq 'require_static_artifacts="$verify_remote_match"' "$SCRIPT"
|
||||
grep -Fq 'require_backend_artifact="$verify_remote_backend"' "$SCRIPT"
|
||||
grep -Fq 'RELEASE_VERIFY_REMOTE_BACKEND=true RELEASE_VERIFY_REMOTE_SCHEMA=true ./scripts/release-preflight.sh' "$DEV_SETUP"
|
||||
grep -Fq 'RELEASE_VERIFY_REMOTE_MATCH=true RELEASE_VERIFY_REMOTE_BACKEND=true RELEASE_VERIFY_REMOTE_SCHEMA=true ./scripts/release-preflight.sh' "$DEV_SETUP"
|
||||
|
||||
echo 'PASS: release preflight supports scoped backend/schema verification and preserves the full-package mode'
|
||||
Reference in New Issue
Block a user