fix(release): close two publish-path verification gaps (§B hardening)
- create-release.sh: assert the freshly built frontend dist actually embeds the bumped version before packaging — the ui-dist-version guard in tests/release/run.sh is behind --with-build, which the release path never passes, so a silently no-opped npm build could ship a stale dist with a valid sha256. Verified against the real dist (passes on current version, trips on a missing one). - publish-release-assets.sh: verify published assets by downloading and comparing sha256 against the manifest, not just Content-Length — a size-correct/content-wrong mirror asset now fails the publish gate. Verified live against the v1.7.99-alpha assets on the vps2 mirror. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
1316b0c0d4
commit
e77ccff0e1
@ -158,6 +158,15 @@ cd "$PROJECT_ROOT/neode-ui"
|
|||||||
npm run build 2>&1 | tail -3
|
npm run build 2>&1 | tail -3
|
||||||
cd "$PROJECT_ROOT"
|
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..."
|
echo "[5/8] Validating curated changelog..."
|
||||||
|
|
||||||
CHANGELOG_FILE="$PROJECT_ROOT/CHANGELOG.md"
|
CHANGELOG_FILE="$PROJECT_ROOT/CHANGELOG.md"
|
||||||
|
|||||||
@ -98,18 +98,24 @@ upload_asset() {
|
|||||||
upload_asset "$BACKEND" "archipelago"
|
upload_asset "$BACKEND" "archipelago"
|
||||||
upload_asset "$FRONTEND" "archipelago-frontend-${VERSION}.tar.gz"
|
upload_asset "$FRONTEND" "archipelago-frontend-${VERSION}.tar.gz"
|
||||||
|
|
||||||
echo "Verifying public download URLs from manifest..."
|
echo "Verifying public download URLs from manifest (size + sha256)..."
|
||||||
python3 - "$PROJECT_ROOT/releases/manifest.json" <<'PY' | while read -r url size; do
|
python3 - "$PROJECT_ROOT/releases/manifest.json" <<'PY' | while read -r url size sha; do
|
||||||
import json
|
import json
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
manifest = json.load(open(sys.argv[1]))
|
manifest = json.load(open(sys.argv[1]))
|
||||||
for component in manifest["components"]:
|
for component in manifest["components"]:
|
||||||
print(component["download_url"], component["size_bytes"])
|
print(component["download_url"], component["size_bytes"], component["sha256"])
|
||||||
PY
|
PY
|
||||||
headers=$(curl -fsSI -L --max-time 60 "$url") || fail "download URL failed: $url"
|
# Full GET, not HEAD: a size-correct/content-wrong mirror asset must fail
|
||||||
actual_size=$(printf '%s\n' "$headers" | awk 'tolower($1)=="content-length:" { size=$2 } END { gsub("\r", "", size); print size }')
|
# the publish gate, so compare the actual bytes against the manifest sha256.
|
||||||
[ "$actual_size" = "$size" ] || fail "download size mismatch for $url (expected $size, got ${actual_size:-missing})"
|
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
|
done
|
||||||
|
|
||||||
echo "Release v${VERSION} published and verified on $REMOTE."
|
echo "Release v${VERSION} published and verified on $REMOTE."
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user