1111
1212env :
1313 CARGO_TERM_COLOR : always
14- RUST_VERSION : 1.84.1
14+ RUST_VERSION : stable
1515
1616jobs :
1717 detect-changes :
7979 echo "No published crates changed in this PR"
8080 else
8181 echo "has_changes=true" >> "$GITHUB_OUTPUT"
82- # Create JSON array
8382 CRATES_JSON=$(printf '%s\n' "${CHANGED_CRATES[@]}" | jq -R . | jq -s .)
8483 echo "crates=$CRATES_JSON" >> "$GITHUB_OUTPUT"
8584 echo "Changed published crates: ${CHANGED_CRATES[*]}"
9291 outputs :
9392 semver_level : ${{ steps.aggregate.outputs.semver_level }}
9493 failed_crates : ${{ steps.aggregate.outputs.failed_crates }}
95- skipped_crates : ${{ steps.aggregate.outputs.skipped_crates }}
9694 steps :
9795 - uses : actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
9896 with :
@@ -109,7 +107,8 @@ jobs:
109107
110108 - name : Install cargo-semver-checks
111109 run : |
112- cargo install cargo-semver-checks --locked --version 0.44.0
110+ apt install libssl-dev # cargo-public-api dependency
111+ cargo install cargo-public-api --locked
113112
114113 - name : Run semver checks on changed crates
115114 id : semver
@@ -129,38 +128,71 @@ jobs:
129128 echo "Checking semver for: $CRATE_NAME"
130129 echo "========================================"
131130
132- # Try to run cargo-semver-checks
131+ # Get the current version from Cargo.toml
132+ CRATE_MANIFEST=$(find . -name Cargo.toml -exec grep -l "name = \"$CRATE_NAME\"" {} \; | head -1)
133+ echo "MANIFEST: $CRATE_MANIFEST"
134+ if [[ -z "$CRATE_MANIFEST" ]]; then
135+ echo "Could not find Cargo.toml for $CRATE_NAME"
136+ continue
137+ fi
138+
139+ CURRENT_VERSION=$(grep "^version" "$CRATE_MANIFEST" | head -1 | sed 's/version[[:space:]]*=[[:space:]]*"\(.*\)"/\1/')
140+ echo "Current version: $CURRENT_VERSION"
141+
142+ # Try to run cargo-public-api diff
133143 set +e
134- cargo semver-checks check-release -- package "$CRATE_NAME" --verbose 2>&1 | tee semver -output.txt
144+ cargo public-api -- package "$CRATE_NAME" diff "$CURRENT_VERSION" 2>&1 | tee api -output.txt
135145 EXIT_CODE=$?
136146 set -e
137147
138148 if [[ $EXIT_CODE -ne 0 ]]; then
139- # Check if failure is due to crate not being published
140- if grep -qE "not found in registry|baseline not found|no baseline found|could not find .* in registry" semver-output.txt; then
141- echo "$CRATE_NAME not published or no baseline available"
142- continue
149+ echo "Unexpected error for $CRATE_NAME (exit code: $EXIT_CODE)"
150+ continue
151+ fi
152+
153+ # Analyze the diff output
154+ LEVEL="none"
155+
156+ # Check for removed items (major change)
157+ if grep -q "Removed items from the public API$" api-output.txt; then
158+ if ! grep -A 2 "^Removed items from the public API$" api-output.txt | grep -q "^(none)$"; then
159+ LEVEL="major"
160+ echo "Detected removed items (major change)"
161+ fi
162+ fi
163+
164+ # Check for changed items (major change)
165+ if grep -q "^Changed items in the public API$" api-output.txt; then
166+ if ! grep -A 2 "^Changed items in the public API$" api-output.txt | grep -q "^(none)$"; then
167+ LEVEL="major"
168+ echo "Detected changed items (major change)"
143169 fi
170+ fi
144171
145- if grep -q "--- failure " semver-output.txt; then
146- if grep -qE "major change|breaking change|function_missing|struct_missing|enum_variant_missing" semver-output.txt; then
147- HIGHEST_LEVEL="major"
148- CRATES_CHECKED+=("$CRATE_NAME:major")
149- elif grep -qE "minor change|function_added|struct_added" semver-output.txt; then
150- if [[ "$HIGHEST_LEVEL" != "major" ]]; then
151- HIGHEST_LEVEL="minor"
152- fi
153- CRATES_CHECKED+=("$CRATE_NAME:minor")
154- else
155- if [[ "$HIGHEST_LEVEL" == "none" ]]; then
156- HIGHEST_LEVEL="patch"
157- fi
158- CRATES_CHECKED+=("$CRATE_NAME:patch")
172+ # Check for added items (minor change) - only if not already major
173+ if [[ "$LEVEL" != "major" ]]; then
174+ if grep -q "Added items to the public API$" api-output.txt; then
175+ if ! grep -A 2 "^Added items to the public API$" api-output.txt | grep -q "^(none)"; then
176+ LEVEL="minor"
177+ echo "Detected added items (minor change)"
159178 fi
160- else
161- echo "Unexpected error for $CRATE_NAME (exit code: $EXIT_CODE)"
179+ fi
180+ fi
181+
182+ # If we detected changes, update the highest level
183+ if [[ "$LEVEL" != "none" ]]; then
184+ CRATES_CHECKED+=("$CRATE_NAME:$LEVEL")
185+
186+ # Update highest level
187+ if [[ "$LEVEL" == "major" ]]; then
188+ HIGHEST_LEVEL="major"
189+ elif [[ "$LEVEL" == "minor" ]] && [[ "$HIGHEST_LEVEL" != "major" ]]; then
190+ HIGHEST_LEVEL="minor"
191+ elif [[ "$HIGHEST_LEVEL" == "none" ]]; then
192+ HIGHEST_LEVEL="patch"
162193 fi
163194 else
195+ # No API changes detected, assume patch level
164196 if [[ "$HIGHEST_LEVEL" == "none" ]]; then
165197 HIGHEST_LEVEL="patch"
166198 fi
@@ -169,19 +201,19 @@ jobs:
169201
170202 # Save results to file for aggregate step
171203 echo "$HIGHEST_LEVEL" > semver-level.txt
172- printf '%s\n' "${CRATES_CHECKED[@]}" > failed-crates .txt || true
204+ printf '%s\n' "${CRATES_CHECKED[@]}" > crates_checked .txt || true
173205
174206 - name : Aggregate results
175207 id : aggregate
176208 run : |
177209 HIGHEST_LEVEL=$(cat semver-level.txt)
178- CRATES_CHECKED=$(cat failed-crates .txt | tr '\n' ' ' || echo "")
210+ CRATES_CHECKED=$(cat crates_checked .txt | tr '\n' ' ' || echo "")
179211
180212 echo "semver_level=$HIGHEST_LEVEL" >> "$GITHUB_OUTPUT"
181- echo "failed_crates =$CRATES_CHECKED" >> "$GITHUB_OUTPUT"
213+ echo "crates_checked =$CRATES_CHECKED" >> "$GITHUB_OUTPUT"
182214
183215 echo "Final aggregate semver level: $HIGHEST_LEVEL"
184- echo "Failed crates : $CRATES_CHECKED"
216+ echo "Crates checked : $CRATES_CHECKED"
185217
186218 validate :
187219 needs : [detect-changes, semver-check]
@@ -192,19 +224,18 @@ jobs:
192224 env :
193225 PR_TITLE : ${{ github.event.pull_request.title }}
194226 SEMVER_LEVEL : ${{ needs.semver-check.outputs.semver_level }}
195- CRATES_CHECKED : ${{ needs.semver-check.outputs.failed_crates }}
196- SKIPPED_CRATES : ${{ needs.semver-check.outputs.skipped_crates }}
227+ CRATES_CHECKED : ${{ needs.semver-check.outputs.crates_checked }}
197228 run : |
198229 set -euo pipefail
199230
200231 echo "PR Title: $PR_TITLE"
201232 echo "Detected semver level: $SEMVER_LEVEL"
202233 echo "Crates with changes: $CRATES_CHECKED"
203- echo "Skipped crates: $SKIPPED_CRATES"
204234
205- Format: type(optional-scope): description
206- Breaking changes: type!: or type(scope)!:
207- if [[ "$PR_TITLE" =~ ^([a-z]+)(\([^)]+\))?(!)?:\ .+ ]]; then
235+ # Format: type(optional-scope): description
236+ # Breaking changes: type!: or type(scope)!:
237+ REGEX='^([a-z]+)(\([^)]+\))?(!)?: .+'
238+ if [[ "$PR_TITLE" =~ $REGEX ]]; then
208239 TYPE="${BASH_REMATCH[1]}"
209240 HAS_BREAKING_MARKER="${BASH_REMATCH[3]}"
210241 else
@@ -221,79 +252,59 @@ jobs:
221252
222253 # Validation rules
223254 case "$TYPE" in
224- fix)
225- # fix: should only have patch-level changes
226- if [[ "$SEMVER_LEVEL" == "major" ]] || [[ "$SEMVER_LEVEL" == "minor" ]]; then
227- VALIDATION_FAILED="true"
228- fi
229- ;;
255+ fix)
256+ if [[ "$SEMVER_LEVEL" == "major" ]] || [[ "$SEMVER_LEVEL" == "minor" ]] || [[ "$SEMVER_LEVEL" == "none" ]]; then
257+ VALIDATION_FAILED="true"
258+ fi
259+ ;;
230260
231- feat)
232- if [[ "$SEMVER_LEVEL" == "major" ]] && [[ -z "$HAS_BREAKING_MARKER" ]]; then
233- VALIDATION_FAILED="true"
234- elif [[ "$SEMVER_LEVEL" == "patch" ]]; then
235- VALIDATION_FAILED="true"
236- fi
237- ;;
261+ feat)
262+ if [[ "$SEMVER_LEVEL" == "major" ]] && [[ -z "$HAS_BREAKING_MARKER" ]]; then
263+ VALIDATION_FAILED="true"
264+ elif [[ "$SEMVER_LEVEL" == "patch" ]] || [[ "$SEMVER_LEVEL" == "none " ]]; then
265+ VALIDATION_FAILED="true"
266+ fi
267+ ;;
238268
239- chore|docs|style|test|ci|build|perf)
240- # These should not change public API
241- if [[ "$SEMVER_LEVEL" == "major" ]] || [[ "$SEMVER_LEVEL" == "minor" ]]; then
242- VALIDATION_FAILED="true"
243- fi
244- ;;
269+ chore|ci|docs|style|test|build|perf)
270+ # Breaking change marker shouldn't be there.
271+ if [[ -z "$HAS_BREAKING_MARKER" ]]; then
272+ VALIDATION_FAILED="true"
273+ fi
245274
246- refactor)
247- # Should we allow refactors to introduce breaking changes.
248- if [[ "$SEMVER_LEVEL" == "major" ]] && [[ -z "$HAS_BREAKING_MARKER" ]]; then
249- VALIDATION_FAILED="true"
250- fi
251- ;;
252-
253- revert)
254- # Revert commits are allowed to have any semver level
255- echo "Revert commits are exempt from semver validation"
256- ;;
257-
258- *)
259- # "refactor" type fall through this category, should we be more specific about it?.
260- echo "Commit type '$TYPE', applying lenient validation"
261- # Only fail on undeclared breaking changes
262- if [[ "$SEMVER_LEVEL" == "major" ]] && [[ -z "$HAS_BREAKING_MARKER" ]]; then
263- VALIDATION_FAILED="true"
264- fi
265- ;;
266- esac
275+ # These should not change public API
276+ if [[ "$SEMVER_LEVEL" == "major" ]] || [[ "$SEMVER_LEVEL" == "minor" ]]; then
277+ VALIDATION_FAILED="true"
278+ fi
279+ ;;
280+
281+
282+ refactor)
283+ if [[ "$SEMVER_LEVEL" == "major" ]] && [[ -z "$HAS_BREAKING_MARKER" ]]; then
284+ VALIDATION_FAILED="true"
285+ fi
286+ ;;
287+
288+ revert)
289+ # Revert commits are allowed to have any semver level
290+ ;;
267291
268- # Check for breaking marker with non-breaking changes
269- if [[ -n "$HAS_BREAKING_MARKER" ]] && [[ "$SEMVER_LEVEL" != "major" ]]; then
292+ *)
293+ echo "$TYPE not handled";
270294 VALIDATION_FAILED="true"
271- fi
295+ ;;
296+ esac
272297
273- # Final result
274298 if [[ "$VALIDATION_FAILED" == "true" ]]; then
275299 echo ""
276300 echo "============================================"
277301 echo "❌ SEMVER VALIDATION FAILED"
278302 echo "============================================"
279303 echo ""
280- echo "$ERROR_MESSAGE"
281- echo ""
282304 echo "Details:"
283305 echo " PR Title: $PR_TITLE"
284306 echo " Detected semver level: $SEMVER_LEVEL"
285- echo " Crates with API changes: $CRATES_CHECKED"
286- echo ""
287- echo "To fix this:"
288- echo " 1. Update your PR title to match the detected changes, OR"
289- echo " 2. Adjust your code changes to match the PR title intent"
290- echo "============================================"
291307 exit 1
292308 else
293- echo ""
294- echo "============================================"
295- echo "✅ SEMVER VALIDATION PASSED"
296- echo "============================================"
297- echo "PR title type matches detected API changes"
298309 exit 0
299310 fi
0 commit comments