diff --git a/scripts/create-release.sh b/scripts/create-release.sh index dad07e0b..3835033d 100755 --- a/scripts/create-release.sh +++ b/scripts/create-release.sh @@ -158,6 +158,15 @@ cd "$PROJECT_ROOT/neode-ui" npm run build 2>&1 | tail -3 cd "$PROJECT_ROOT" +# npm run build can silently no-op (vue-tsc EACCES burned us before) — a stale +# dist would ship with a perfectly valid sha256. Require the freshly built +# bundle to embed the version we just bumped to before it gets packaged. +if ! grep -rqo "${VERSION}" "$PROJECT_ROOT"/web/dist/neode-ui/assets/*.js; then + echo "Error: web/dist/neode-ui does not contain v${VERSION} — the frontend" >&2 + echo " build no-opped or its output is stale. Aborting release." >&2 + exit 1 +fi + echo "[5/8] Validating curated changelog..." CHANGELOG_FILE="$PROJECT_ROOT/CHANGELOG.md" diff --git a/scripts/publish-release-assets.sh b/scripts/publish-release-assets.sh index 1527ca9a..1650d0a9 100755 --- a/scripts/publish-release-assets.sh +++ b/scripts/publish-release-assets.sh @@ -98,18 +98,24 @@ upload_asset() { upload_asset "$BACKEND" "archipelago" upload_asset "$FRONTEND" "archipelago-frontend-${VERSION}.tar.gz" -echo "Verifying public download URLs from manifest..." -python3 - "$PROJECT_ROOT/releases/manifest.json" <<'PY' | while read -r url size; do +echo "Verifying public download URLs from manifest (size + sha256)..." +python3 - "$PROJECT_ROOT/releases/manifest.json" <<'PY' | while read -r url size sha; do import json import sys manifest = json.load(open(sys.argv[1])) for component in manifest["components"]: - print(component["download_url"], component["size_bytes"]) + print(component["download_url"], component["size_bytes"], component["sha256"]) PY - headers=$(curl -fsSI -L --max-time 60 "$url") || fail "download URL failed: $url" - actual_size=$(printf '%s\n' "$headers" | awk 'tolower($1)=="content-length:" { size=$2 } END { gsub("\r", "", size); print size }') - [ "$actual_size" = "$size" ] || fail "download size mismatch for $url (expected $size, got ${actual_size:-missing})" + # Full GET, not HEAD: a size-correct/content-wrong mirror asset must fail + # the publish gate, so compare the actual bytes against the manifest sha256. + tmp=$(mktemp) + curl -fsSL --max-time 900 -o "$tmp" "$url" || { rm -f "$tmp"; fail "download URL failed: $url"; } + actual_size=$(stat -c %s "$tmp") + actual_sha=$(sha256sum "$tmp" | awk '{print $1}') + rm -f "$tmp" + [ "$actual_size" = "$size" ] || fail "download size mismatch for $url (expected $size, got $actual_size)" + [ "$actual_sha" = "$sha" ] || fail "download sha256 mismatch for $url (expected $sha, got $actual_sha)" done echo "Release v${VERSION} published and verified on $REMOTE."