diff --git a/CHANGELOG.md b/CHANGELOG.md index a9118e3..230243e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,13 +7,11 @@ and this project follows [Semantic Versioning](https://semver.org/spec/v2.0.0.ht ## [Unreleased] -### Added -- Added `python -m devr.release_preflight` to run release artifact smoke tests and validate changelog/version consistency before tagging. - ## [0.1.0] - 2026-02-17 ### Added - Added `devr doctor` for environment diagnostics and setup troubleshooting. +- Added `python -m devr.release_preflight` to run release artifact smoke tests and validate changelog/version consistency before tagging. ### Changed - Improved `devr check` diagnostics with explicit stage headers and command summaries. diff --git a/src/devr/release_preflight.py b/src/devr/release_preflight.py index a8add12..78eb8e9 100644 --- a/src/devr/release_preflight.py +++ b/src/devr/release_preflight.py @@ -46,6 +46,7 @@ def changelog_versions(changelog_path: Path) -> list[str]: def validate_changelog(changelog_path: Path, version: str) -> None: """Validate changelog has expected release structure for ``version``.""" + changelog_text = changelog_path.read_text(encoding="utf-8") versions = changelog_versions(changelog_path) if not versions or versions[0] != "Unreleased": raise ReleasePreflightError( @@ -57,6 +58,17 @@ def validate_changelog(changelog_path: Path, version: str) -> None: "Move completed entries from Unreleased into that release section before tagging." ) + unreleased_match = re.search( + r"^## \[Unreleased\]\s*(.*?)(?=^## \[|\Z)", + changelog_text, + flags=re.MULTILINE | re.DOTALL, + ) + if unreleased_match and unreleased_match.group(1).strip(): + raise ReleasePreflightError( + "CHANGELOG.md has unreleased entries. Move completed entries from " + "'Unreleased' into the current version section before tagging." + ) + def run_checked(cmd: list[str], cwd: Path) -> None: """Run command and fail with a clear message on non-zero exit.""" diff --git a/tests/test_release_preflight.py b/tests/test_release_preflight.py index 00f3742..1fab810 100644 --- a/tests/test_release_preflight.py +++ b/tests/test_release_preflight.py @@ -35,6 +35,17 @@ def test_validate_changelog_requires_current_version_section(tmp_path: Path) -> validate_changelog(changelog, "0.1.0") +def test_validate_changelog_requires_empty_unreleased_section(tmp_path: Path) -> None: + changelog = tmp_path / "CHANGELOG.md" + changelog.write_text( + "## [Unreleased]\n\n### Added\n- pending item\n\n## [0.1.0] - 2026-01-01\n", + encoding="utf-8", + ) + + with pytest.raises(ReleasePreflightError, match="unreleased entries"): + validate_changelog(changelog, "0.1.0") + + def test_changelog_versions_extracts_headings(tmp_path: Path) -> None: changelog = tmp_path / "CHANGELOG.md" changelog.write_text(