diff --git a/actions/auto-release/action.yml b/actions/auto-release/action.yml index 9d2ae59..8599577 100644 --- a/actions/auto-release/action.yml +++ b/actions/auto-release/action.yml @@ -94,7 +94,7 @@ runs: esac fi - # --- commit gate: subject line only; 0=match, 1=no-match, >1=error --- + # --- commit gate: 0=release, 1=valid non-release skip, >1=error --- subject="${COMMIT_MESSAGE%%$'\n'*}" set +e cc_out="$(bash "$ccheck" release-gate "$subject" 2>&1)" # capture, don't suppress @@ -102,7 +102,7 @@ runs: set -e case "$rc" in 0) commit_ok=true ;; - 1) commit_ok=false ;; # no-match: cc_out is the expected ::error::, ignore it + 1) commit_ok=false ;; # valid conventional non-release subject: skip *) echo "$cc_out"; echo "::error::conventional-commit check failed (rc=$rc)"; exit 1 ;; esac diff --git a/actions/auto-release/test_gate.sh b/actions/auto-release/test_gate.sh index 59fa2a8..be4bed9 100755 --- a/actions/auto-release/test_gate.sh +++ b/actions/auto-release/test_gate.sh @@ -68,5 +68,32 @@ bash gate.sh validate-version "3" >/dev/null 2>&1 && bad "reject 3" || [ "$(bash gate.sh compute-tag v 3.1 150)" = "v3.1.150" ] && ok "tag v" || bad "tag v" [ "$(bash gate.sh compute-tag jtk-v 1.0 42)" = "jtk-v1.0.42" ] && ok "tag jtk-v" || bad "tag jtk-v" +# --- release commit gate: release, intentional skip, and fail-loud invalid subject --- +CCHECK="../conventional-commit/check.sh" +check_rc() { + local mode="$1" message="$2" output rc + if output="$(bash "$CCHECK" "$mode" "$message" 2>&1)"; then rc=0; else rc=$?; fi + printf '%s\n' "$rc|$output" +} +expect_rc() { + local label="$1" expected="$2" result rc + result="$(check_rc "$3" "$4")" + rc="${result%%|*}" + if [ "$rc" -eq "$expected" ]; then ok "$label"; else bad "$label (rc=$rc)"; fi +} +expect_rc "feat release" 0 release-gate "feat: ship it" +expect_rc "fix release" 0 release-gate "fix(scope)!: stop the bug" +expect_rc "docs skip" 1 release-gate "docs: update the guide" +expect_rc "refactor skip" 1 release-gate "refactor(core): simplify the path" +expect_rc "ci skip" 1 release-gate "ci: update automation" +invalid="$(check_rc release-gate "Fix scoped reviewer workspace path validation (#533)")" +invalid_rc="${invalid%%|*}" +invalid_output="${invalid#*|}" +case "$invalid_output" in + *"invalid landed commit subject; refusing to skip auto-release"*) + [ "$invalid_rc" -eq 2 ] && ok "invalid landed subject fails loudly" || bad "invalid landed subject rc=$invalid_rc" ;; + *) bad "invalid landed subject message" ;; +esac + echo "----" if [ "$fails" -eq 0 ]; then echo "all gate.sh tests passed"; else echo "$fails failed"; exit 1; fi diff --git a/actions/conventional-commit/check.sh b/actions/conventional-commit/check.sh index 9b9df02..5431001 100755 --- a/actions/conventional-commit/check.sh +++ b/actions/conventional-commit/check.sh @@ -5,7 +5,7 @@ # # Usage: check.sh # title accept the full conventional-commit type set -# release-gate accept only feat|fix (the release-cutting subset) +# release-gate accept feat|fix, skip valid non-release types, reject malformed subjects set -euo pipefail mode="${1:-}" @@ -16,9 +16,11 @@ if [ -z "$mode" ] || [ -z "$message" ]; then exit 2 fi +title_pattern='^(feat|fix|refactor|test|docs|ci|chore|build|perf|style)(\([^)]+\))?!?: .+' + case "$mode" in title) - pattern='^(feat|fix|refactor|test|docs|ci|chore|build|perf|style)(\([^)]+\))?!?: .+' + pattern="$title_pattern" ;; release-gate) pattern='^(feat|fix)(\([^)]+\))?!?: .+' @@ -33,6 +35,19 @@ if printf '%s' "$message" | grep -Eq "$pattern"; then exit 0 fi +# release-gate exit 1 is reserved for a valid conventional commit that is not +# release-worthy. A malformed landed subject must fail the workflow instead of +# being mistaken for an intentional skip. +if [ "$mode" = "release-gate" ] && printf '%s' "$message" | grep -Eq "$title_pattern"; then + exit 1 +fi + +if [ "$mode" = "release-gate" ]; then + echo "::error::invalid landed commit subject; refusing to skip auto-release: $message" >&2 + echo "expected pattern: $title_pattern" >&2 + exit 2 +fi + echo "::error::not a conventional commit (mode=$mode): $message" echo "expected pattern: $pattern" >&2 exit 1