|
| 1 | +# Ensure the project version is incremented in every PR before it can merge to the default branch. |
| 2 | +# Looks for BOTH package.json and Cargo.toml: |
| 3 | +# - only package.json present -> its version must increase vs the base branch |
| 4 | +# - only Cargo.toml present -> its version must increase vs the base branch |
| 5 | +# - BOTH present -> both must increase AND equal each other on this branch |
| 6 | +# - neither present -> nothing to enforce (passes) |
| 7 | +# Version files added new on this branch (absent on base) pass. Modeled on Chia-Network/cadt. |
| 8 | +name: Check Version Increment |
| 9 | + |
| 10 | +on: |
| 11 | + pull_request: |
| 12 | + branches: |
| 13 | + - main |
| 14 | + |
| 15 | +# Least privilege: the version check only reads the checked-out branches (GHAS/CodeQL requirement). |
| 16 | +permissions: |
| 17 | + contents: read |
| 18 | + |
| 19 | +concurrency: |
| 20 | + group: ${{ github.ref }}-${{ github.workflow }}-${{ github.event_name }} |
| 21 | + cancel-in-progress: true |
| 22 | + |
| 23 | +jobs: |
| 24 | + check-version: |
| 25 | + name: Check version increment |
| 26 | + runs-on: ubuntu-latest |
| 27 | + steps: |
| 28 | + - name: Checkout PR branch |
| 29 | + uses: actions/checkout@v4 |
| 30 | + with: |
| 31 | + path: branch-repo |
| 32 | + |
| 33 | + - name: Checkout base branch |
| 34 | + uses: actions/checkout@v4 |
| 35 | + with: |
| 36 | + ref: main |
| 37 | + path: base-repo |
| 38 | + |
| 39 | + - name: Check version increment |
| 40 | + shell: bash |
| 41 | + run: | |
| 42 | + set -euo pipefail |
| 43 | +
|
| 44 | + # Extract a version from package.json (.version) or Cargo.toml |
| 45 | + # ([package].version or [workspace.package].version). Empty string if |
| 46 | + # the file or the field is absent. |
| 47 | + pkg_version() { |
| 48 | + local f="$1/package.json" |
| 49 | + [ -f "$f" ] || { echo ""; return; } |
| 50 | + jq -r '.version // ""' "$f" 2>/dev/null || echo "" |
| 51 | + } |
| 52 | + # [package].version, or [workspace.package].version for workspace roots / |
| 53 | + # members that inherit ({ version.workspace = true }). Empty if absent. |
| 54 | + cargo_version() { |
| 55 | + local f="$1/Cargo.toml" |
| 56 | + [ -f "$f" ] || { echo ""; return; } |
| 57 | + python3 -c 'import sys,tomllib; d=tomllib.load(open(sys.argv[1],"rb")); v=d.get("package",{}).get("version") or d.get("workspace",{}).get("package",{}).get("version") or ""; print(v if isinstance(v,str) else "")' "$f" 2>/dev/null || echo "" |
| 58 | + } |
| 59 | +
|
| 60 | + # strictly-greater semver-ish compare using sort -V; true when $2 > $1 |
| 61 | + greater() { [ "$1" != "$2" ] && [ "$(printf '%s\n%s\n' "$1" "$2" | sort -V | tail -1)" = "$2" ]; } |
| 62 | +
|
| 63 | + B=base-repo |
| 64 | + H=branch-repo |
| 65 | + has_pkg=false; has_cargo=false |
| 66 | + [ -f "$H/package.json" ] && has_pkg=true |
| 67 | + [ -f "$H/Cargo.toml" ] && has_cargo=true |
| 68 | +
|
| 69 | + if [ "$has_pkg" = false ] && [ "$has_cargo" = false ]; then |
| 70 | + echo "No package.json or Cargo.toml at repo root — no version gate to enforce." |
| 71 | + exit 0 |
| 72 | + fi |
| 73 | +
|
| 74 | + fail=0 |
| 75 | +
|
| 76 | + if [ "$has_pkg" = true ]; then |
| 77 | + bp=$(pkg_version "$B"); hp=$(pkg_version "$H") |
| 78 | + echo "package.json: base='$bp' head='$hp'" |
| 79 | + if [ -n "$bp" ]; then |
| 80 | + if ! greater "$bp" "$hp"; then |
| 81 | + echo "::error::package.json version must be incremented ($bp -> $hp) before merging." |
| 82 | + fail=1 |
| 83 | + fi |
| 84 | + else |
| 85 | + echo "package.json is new on this branch (no base version) — OK." |
| 86 | + fi |
| 87 | + fi |
| 88 | +
|
| 89 | + if [ "$has_cargo" = true ]; then |
| 90 | + bc=$(cargo_version "$B"); hc=$(cargo_version "$H") |
| 91 | + echo "Cargo.toml: base='$bc' head='$hc'" |
| 92 | + if [ -n "$bc" ]; then |
| 93 | + if ! greater "$bc" "$hc"; then |
| 94 | + echo "::error::Cargo.toml version must be incremented ($bc -> $hc) before merging." |
| 95 | + fail=1 |
| 96 | + fi |
| 97 | + else |
| 98 | + echo "Cargo.toml is new on this branch (no base version) — OK." |
| 99 | + fi |
| 100 | + fi |
| 101 | +
|
| 102 | + if [ "$has_pkg" = true ] && [ "$has_cargo" = true ]; then |
| 103 | + hp=$(pkg_version "$H"); hc=$(cargo_version "$H") |
| 104 | + if [ -n "$hp" ] && [ -n "$hc" ] && [ "$hp" != "$hc" ]; then |
| 105 | + echo "::error::package.json ($hp) and Cargo.toml ($hc) versions must match each other." |
| 106 | + fail=1 |
| 107 | + fi |
| 108 | + fi |
| 109 | +
|
| 110 | + exit $fail |
0 commit comments