From 64701d5d74274441eaff745c156d4703aac7557d Mon Sep 17 00:00:00 2001 From: Sebastian Mendel Date: Thu, 16 Jul 2026 21:38:01 +0200 Subject: [PATCH 1/2] fix(validate): warn on untagged prepared release, skip private package.json MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Version files ahead of the newest tag mean a 'chore: release X.Y.Z' commit merged but the tag never shipped (e.g. cancelled release run) — surface it as WARN so the releaser decides whether to tag or roll it into the next version. A 'private': true package.json is local tooling, not a release surface — exclude it from the version-sync check instead of failing every run. Signed-off-by: Sebastian Mendel --- .../scripts/validate-pre-release.sh | 27 +++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/skills/github-release/scripts/validate-pre-release.sh b/skills/github-release/scripts/validate-pre-release.sh index 6b7e643..5bd45c0 100755 --- a/skills/github-release/scripts/validate-pre-release.sh +++ b/skills/github-release/scripts/validate-pre-release.sh @@ -47,6 +47,14 @@ if [[ -x "${SCRIPT_DIR}/detect-ecosystem.sh" ]]; then file="${line#version-file:}" path="${file%%:*}" ver="${file#*:}" + # A "private": true package.json is local tooling, not a + # publishable release surface — its version never tracks the + # release and would fail the sync check on every run. + if [[ "$path" == package.json || "$path" == package-lock.json ]] \ + && [[ -f package.json ]] \ + && grep -qE '"private"[[:space:]]*:[[:space:]]*true' package.json; then + continue + fi if [[ -n "$ver" ]]; then versions+=("$ver") version_files+=("${path}=${ver}") @@ -69,6 +77,25 @@ else fi fi +# --------------------------------------------------------------------------- +# 1b. Version files ahead of the latest tag (phantom / untagged release) +# --------------------------------------------------------------------------- +# A "chore: release X.Y.Z" commit that was merged but never tagged leaves the +# version files ahead of the newest tag — the prepared release silently never +# shipped (e.g. its tag-triggered workflow was cancelled). Surface it so the +# releaser decides deliberately whether to tag it or roll it into the next +# version. +if ((${#versions[@]} > 0)); then + file_version=$(printf '%s\n' "${versions[@]}" | sort -u | head -1) + latest_tag=$(git tag --list 'v[0-9]*' --sort=-v:refname | head -1) + if [[ -n "$latest_tag" && "v${file_version}" != "$latest_tag" ]]; then + if [[ "$(printf '%s\n' "${latest_tag#v}" "$file_version" | sort -V | tail -1)" == "$file_version" ]]; then + check "WARN" "Version files ahead of latest tag" \ + "files say ${file_version}, newest tag is ${latest_tag} — a prepared release was never tagged" + fi + fi +fi + # --------------------------------------------------------------------------- # 2. CHANGELOG.md has [Unreleased] section with content # --------------------------------------------------------------------------- From 26bd4a4e0721128c94306009ce4b35c96f12e11e Mon Sep 17 00:00:00 2001 From: Sebastian Mendel Date: Thu, 16 Jul 2026 21:44:33 +0200 Subject: [PATCH 2/2] fix(validate): compare highest file version against the tag, semver-sorted sort -u | head -1 picked the lexicographically lowest version when files disagree, which could skip the ahead-of-tag warning. Signed-off-by: Sebastian Mendel --- skills/github-release/scripts/validate-pre-release.sh | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/skills/github-release/scripts/validate-pre-release.sh b/skills/github-release/scripts/validate-pre-release.sh index 5bd45c0..0dd6f71 100755 --- a/skills/github-release/scripts/validate-pre-release.sh +++ b/skills/github-release/scripts/validate-pre-release.sh @@ -86,7 +86,10 @@ fi # releaser decides deliberately whether to tag it or roll it into the next # version. if ((${#versions[@]} > 0)); then - file_version=$(printf '%s\n' "${versions[@]}" | sort -u | head -1) + # Highest file version, semver-sorted — when files are out of sync the + # sync check above already FAILed; comparing the highest against the tag + # still yields the correct "a prepared release was never tagged" signal. + file_version=$(printf '%s\n' "${versions[@]}" | sort -uV | tail -1) latest_tag=$(git tag --list 'v[0-9]*' --sort=-v:refname | head -1) if [[ -n "$latest_tag" && "v${file_version}" != "$latest_tag" ]]; then if [[ "$(printf '%s\n' "${latest_tag#v}" "$file_version" | sort -V | tail -1)" == "$file_version" ]]; then