2026-05-01 05:38:16 +05:00
|
|
|
import re
|
2026-05-03 06:35:50 +05:00
|
|
|
import sys
|
2026-05-01 05:38:16 +05:00
|
|
|
from pathlib import Path
|
|
|
|
|
|
2026-05-03 06:35:50 +05:00
|
|
|
|
2026-05-01 05:38:16 +05:00
|
|
|
def check_release_notes():
|
|
|
|
|
changelog_path = Path("CHANGELOG.md")
|
|
|
|
|
if not changelog_path.exists():
|
2026-05-17 23:28:29 -07:00
|
|
|
print("=" * 60)
|
2026-05-03 05:03:56 +05:00
|
|
|
print(" DEPLOYMENT BLOCKED: CHANGELOG.md MISSING")
|
2026-05-17 23:28:29 -07:00
|
|
|
print("=" * 60)
|
2026-05-03 05:03:56 +05:00
|
|
|
print("Error: CHANGELOG.md not found in repository root.")
|
2026-05-01 05:38:16 +05:00
|
|
|
return False
|
|
|
|
|
|
|
|
|
|
with open(changelog_path, "r") as f:
|
|
|
|
|
content = f.read()
|
|
|
|
|
|
|
|
|
|
# Find the [Unreleased] section
|
|
|
|
|
# It starts with ## [Unreleased] and ends at the next ## header or end of file
|
|
|
|
|
pattern = r"## \[Unreleased\](.*?)(?=\n## |$)"
|
|
|
|
|
match = re.search(pattern, content, re.DOTALL)
|
|
|
|
|
|
|
|
|
|
if not match:
|
2026-05-17 23:28:29 -07:00
|
|
|
print("=" * 60)
|
2026-05-03 05:03:56 +05:00
|
|
|
print(" DEPLOYMENT BLOCKED: [Unreleased] SECTION MISSING")
|
2026-05-17 23:28:29 -07:00
|
|
|
print("=" * 60)
|
2026-05-01 05:38:16 +05:00
|
|
|
print("Error: [Unreleased] section not found in CHANGELOG.md")
|
2026-05-03 05:03:56 +05:00
|
|
|
print("REMEDIATION: Add a '## [Unreleased]' header to CHANGELOG.md.")
|
2026-05-01 05:38:16 +05:00
|
|
|
return False
|
|
|
|
|
|
|
|
|
|
unreleased_content = match.group(1).strip()
|
|
|
|
|
|
|
|
|
|
# Check if there is any actual content (ignoring empty subheaders)
|
|
|
|
|
# We look for lines starting with '-' or containing actual text
|
|
|
|
|
lines = unreleased_content.splitlines()
|
|
|
|
|
has_content = False
|
|
|
|
|
for line in lines:
|
|
|
|
|
stripped = line.strip()
|
2026-05-17 23:28:29 -07:00
|
|
|
if (
|
|
|
|
|
stripped
|
|
|
|
|
and not stripped.startswith("###")
|
|
|
|
|
and not stripped.startswith("##")
|
|
|
|
|
and any(c.isalnum() for c in stripped)
|
|
|
|
|
):
|
|
|
|
|
# If it's not a header and not empty and has alnum, it's likely content
|
|
|
|
|
has_content = True
|
|
|
|
|
break
|
2026-05-01 05:38:16 +05:00
|
|
|
|
|
|
|
|
if not has_content:
|
2026-05-10 05:18:08 +05:00
|
|
|
print("=" * 60)
|
2026-05-03 05:03:56 +05:00
|
|
|
print(" DEPLOYMENT BLOCKED: RELEASE NOTES EMPTY")
|
2026-05-10 05:18:08 +05:00
|
|
|
print("=" * 60)
|
2026-05-03 05:03:56 +05:00
|
|
|
print("Error: [Unreleased] section in CHANGELOG.md exists but has no descriptive content.")
|
|
|
|
|
print("REMEDIATION: Add details about your changes under the [Unreleased] section.")
|
2026-05-10 05:18:08 +05:00
|
|
|
print("Follow Conventional Commits format (e.g., - feat: describe feature).")
|
|
|
|
|
print("=" * 60)
|
2026-05-01 05:38:16 +05:00
|
|
|
return False
|
|
|
|
|
|
2026-05-03 05:03:56 +05:00
|
|
|
print("SUCCESS: [Unreleased] section has content. Release notes validation passed.")
|
2026-05-01 05:38:16 +05:00
|
|
|
return True
|
|
|
|
|
|
2026-05-17 23:28:29 -07:00
|
|
|
|
2026-05-01 05:38:16 +05:00
|
|
|
if __name__ == "__main__":
|
|
|
|
|
if not check_release_notes():
|
|
|
|
|
sys.exit(1)
|
|
|
|
|
sys.exit(0)
|