diff --git a/.github/workflows/update-versions.yml b/.github/workflows/update-versions.yml index 746fd9b..7917fc6 100644 --- a/.github/workflows/update-versions.yml +++ b/.github/workflows/update-versions.yml @@ -33,6 +33,9 @@ jobs: env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + - name: Audit versions.json (key == shipped version per rev) + run: ./scripts/fetch-node-versions.sh audit + - name: Commit changes if versions.json changed id: commit run: | diff --git a/scripts/fetch-node-versions.sh b/scripts/fetch-node-versions.sh index 1c6449f..847089e 100755 --- a/scripts/fetch-node-versions.sh +++ b/scripts/fetch-node-versions.sh @@ -177,6 +177,26 @@ extract_version() { echo "$msg" | grep -oP "${attr_re}.*?\K[0-9]+\.[0-9]+" | head -1 || true } +# Confirm that pkgs/development/web/nodejs/v.nix at $rev actually sets +# `version = ".x"`. A rev's commit message is a label, not a +# guarantee - this is the source of truth for what the rev actually builds. +verify_shipped_version() { + local -r major="$1" version="$2" rev="$3" + local -r url="https://raw.githubusercontent.com/${NIXPKGS_REPO}/${rev}/pkgs/development/web/nodejs/v${major}.nix" + local vfile attempt + # Retry the fetch: a transient network/CDN blip must not be mistaken for a + # real version mismatch (which would wrongly fail the audit / block an add). + for attempt in 1 2 3; do + if vfile=$(curl -sf "$url" 2>/dev/null); then + grep -qP "version\s*=\s*\"${version//./\\.}\.[0-9]+" <<<"$vfile" + return + fi + ((attempt < 3)) && sleep "$attempt" + done + log warn "Could not fetch v${major}.nix for rev ${rev:0:7} after 3 attempts" + return 1 +} + is_valid_version() { local -r version="$1" [[ "$version" =~ ^[0-9]+\.[0-9]+$ ]] @@ -225,6 +245,11 @@ add_version() { return 0 fi + verify_shipped_version "$major" "$version" "$rev" || { + log error "Rev ${rev:0:7} does not ship ${version}.x (checked v${major}.nix); refusing to add" + return 1 + } + log info "Adding Node.js $version..." local sha256 @@ -333,16 +358,45 @@ cmd_add() { for entry in "${commits[@]}"; do [[ -z "$entry" ]] && continue local sha="${entry%%|*}" msg="${entry#*|}" - if echo "$msg" | grep -q "$version"; then - log info "Found commit: ${sha:0:7}" - add_version "$version" "$sha" - return 0 - fi + local extracted + extracted=$(extract_version "$msg") + [[ "$extracted" == "$version" ]] || continue + + log info "Found commit: ${sha:0:7} ($msg)" + add_version "$version" "$sha" && return 0 + log warn "Commit ${sha:0:7} rejected, continuing search..." done die "Could not find Node.js $version in nixpkgs" } +cmd_audit() { + [[ -f "$VERSIONS_FILE" ]] || die "versions.json not found" + + log info "Auditing versions.json: key == shippedVersion(rev) for each entry..." + + local -a entries + mapfile -t entries < <(jq -r '.versions | to_entries[] | "\(.key)|\(.value.rev)"' "$VERSIONS_FILE") + + local fail=0 + for entry in "${entries[@]}"; do + local key="${entry%%|*}" rev="${entry#*|}" + local major="${key%%.*}" + if verify_shipped_version "$major" "$key" "$rev"; then + log info "OK $key (rev ${rev:0:7})" + else + log error "FAIL $key (rev ${rev:0:7}) does not ship ${key}.x per v${major}.nix" + ((fail++)) + fi + done + + if ((fail > 0)); then + die "$fail entr$([[ $fail -eq 1 ]] && echo y || echo ies) failed the shippedVersion audit" + fi + + log info "Audit passed: all ${#entries[@]} entries match their pinned rev." +} + usage() { cat < Add specific version (e.g., 22.12) + audit Verify every entry's rev actually ships its key version --dry-run Show what would be added without making changes Options: @@ -390,6 +445,7 @@ main() { case "${1:-}" in list) cmd_list ;; add) cmd_add "${2:-}" ;; + audit) cmd_audit ;; "") cmd_discover "$dry_run" ;; *) usage diff --git a/versions.json b/versions.json index 202d5f9..33ca9b6 100644 --- a/versions.json +++ b/versions.json @@ -98,8 +98,8 @@ }, "22.0": { "version": "22.0", - "rev": "8a9a1e6369d83265d9a21dc62ced894b3e08edb4", - "sha256": "181rvmx8zvf6jyjk4v8znyxksaclnq51bidpbnnpk2pg1wlgvczb", + "rev": "ba733f8000925e837e30765f273fec153426403d", + "sha256": "05d31rs43nfw1hkgnhqpplrr32s96bc8fll66g32f0g41gjby9wq", "attr": "nodejs_22" }, "22.4": { @@ -182,20 +182,20 @@ }, "24.13": { "version": "24.13", - "rev": "9c0e2056b3c16190aafe67e4d29e530fc1f8c7c7", - "sha256": "0pkjlvjpvhsm078v0qb9i0y1vx3rgdpvk8wikvbxn0wldm0xkwps", + "rev": "a2cd48be3ba60ff87ddb7b5533e4185959f9f511", + "sha256": "15qh6s98abj5n5776bfhqgpbwahqqn4dcalwy1822b7p21cbyqn1", "attr": "nodejs_24" }, "24.14": { "version": "24.14", - "rev": "0968bb28e2dd918c3228895ef76ffda0fdf1b5f3", - "sha256": "19730d82hvl9s7l8brl3q5xl9qkhjnza1gi9666wgl87cwigq0x3", + "rev": "d0a406bf79d281e5bfbede3a7721b4d6115bcb2c", + "sha256": "17g4hqfvv3ljf2xlxl1rfyv7hqs81z1g97phc05xc6z65g867yzg", "attr": "nodejs_24" }, "24.15": { "version": "24.15", - "rev": "0968bb28e2dd918c3228895ef76ffda0fdf1b5f3", - "sha256": "19730d82hvl9s7l8brl3q5xl9qkhjnza1gi9666wgl87cwigq0x3", + "rev": "227c82a6ca851558a552111bf51824088a2df980", + "sha256": "1z7a376s8nqbz35lf550l85rjyai4c0lkr0xn87k2w4104dwi4h4", "attr": "nodejs_24" }, "25.2": { @@ -218,26 +218,26 @@ }, "25.5": { "version": "25.5", - "rev": "0801ac780b7116dda2c7b396f7b49c01367853c4", - "sha256": "1w4yfkvkgf6a3l51s7nfq8ncjirc0brfbr62y05wzcmfxqq4vrrr", + "rev": "dd0dc3d7a766e9a2bb283be80f60e16cfdaabb90", + "sha256": "0si6553rd1c3m67q0vb8mi3cl4fycw754pjnlhcjc43xlvjsapqb", "attr": "nodejs_25" }, "25.6": { "version": "25.6", - "rev": "699d7d142df2cfacc4837b6b8d9d051b1b508759", - "sha256": "0f94sywarrsk0gldfa92nkw3sdc5v3b1xanhwpq2qicv3navd9a9", + "rev": "20d148068e415c4a68f4c97ae1856bc000949d10", + "sha256": "19cx7f3ly9pifhnaykvpav0s8y5fvaa1ln0aadiqkwpy03n7rr9k", "attr": "nodejs_25" }, "25.7": { "version": "25.7", - "rev": "0d60a07834d66731ade3c0368db89e29eb722f7d", - "sha256": "0jv21jn1iaf2h2na063r1q91xd2nxncrgjxdnbadr0i0b6gyzci3", + "rev": "699d7d142df2cfacc4837b6b8d9d051b1b508759", + "sha256": "0f94sywarrsk0gldfa92nkw3sdc5v3b1xanhwpq2qicv3navd9a9", "attr": "nodejs_25" }, "25.8": { "version": "25.8", - "rev": "94d8af17424dfc818024d98a59a0a1e0013495e6", - "sha256": "1yymvjk5ibxf7j9331i85bdnkmb74hvlha1156znfgsv5h5v6k64", + "rev": "a867e4c950e0ef01887be00a97f8b07861f8670e", + "sha256": "1h5zs8lxdw082n6apvw9ij9ppks4c6n55f8s7fwlq059ckqlg7mn", "attr": "nodejs_25" }, "25.9": { @@ -248,20 +248,20 @@ }, "26.0": { "version": "26.0", - "rev": "2ebf37ab0d456633dc5c29df64e1f97739eb7e56", - "sha256": "0c86sbsasjjxqs86v829kbvq6jdgywrs00rnixn5r6ihh7qycdf5", + "rev": "f47430fef5df8a398780068cdd390bab32e64eab", + "sha256": "1d9z2y1i1fr6dckprwkrrxw345aischpqrrp7dvmcpl48bd124ak", "attr": "nodejs_26" }, "26.1": { "version": "26.1", - "rev": "0f94bb0d8954e8368a8ef716135c89f3143de178", - "sha256": "18jjhvbhb7s2qlnpq4phd8jjxywxdymi5l8p3q5icc6dygw16p00", + "rev": "2ebf37ab0d456633dc5c29df64e1f97739eb7e56", + "sha256": "0c86sbsasjjxqs86v829kbvq6jdgywrs00rnixn5r6ihh7qycdf5", "attr": "nodejs_26" }, "26.2": { "version": "26.2", - "rev": "302d9e77f10a189ad64227c5a0e474e4ff5fb9eb", - "sha256": "03y4gzn98ls2yrafq9hycx1j2zsbydc1cx6wqmrfyjz828njrg5f", + "rev": "0f94bb0d8954e8368a8ef716135c89f3143de178", + "sha256": "18jjhvbhb7s2qlnpq4phd8jjxywxdymi5l8p3q5icc6dygw16p00", "attr": "nodejs_26" }, "26.3": {