Compare commits

..

2 Commits
v2.0.0 ... main

Author SHA1 Message Date
Kevin Veen-Birkenbach
a4b29b4d49 Release version 2.0.1
Some checks are pending
CI / security-codeql (push) Waiting to run
CI / test-unit (push) Waiting to run
CI / test-integration (push) Waiting to run
CI / test-env-virtual (push) Waiting to run
CI / test-env-nix (push) Waiting to run
CI / test-e2e (push) Waiting to run
CI / test-virgin-user (push) Waiting to run
CI / test-virgin-root (push) Waiting to run
CI / lint-shell (push) Waiting to run
CI / lint-python (push) Waiting to run
CI / lint-docker (push) Waiting to run
Mark stable commit / mark-stable (push) Waiting to run
2026-09-18 17:15:35 +02:00
Kevin Veen-Birkenbach
4e83779459 fix(release): do not end a changelog on a blank line
An entry carries a trailing blank line so that a `## [version]` below it is
separated. Two of the four placements in _insert_after_h1 put the entry at the
end of the file instead, where that separator becomes a blank line after the
final newline and markdownlint reports MD012. Both now pass the entry through
_at_end_of_file.

Only the first entry a file ever receives is affected, which is why it appears
on a repository's opening release and never again: every later entry is
inserted above an existing H2, and there the separator is correct. The
base-images repository hit it on 1.0.0 and its lint job went red on the first
push it ever had.

The other two placements are deliberately untouched. In the legacy branch the
old content follows the entry, and before the first H2 the previous release
section does, so the blank line has to stay.

Verified: four tests, one per placement, assert both that no document holds
three consecutive newlines and that it ends on exactly one; two of them also
assert the separator survives where it belongs, which fails if the trim is
applied too widely. 74 tests pass under tests/unit/pkgmgr/actions/release.

Unrelated and pre-existing: test_update_changelog_transforms_heading_and_inline_code
fails wherever markdownlint-cli2 is installed, because transform_changelog_message
renders a leading `#` as bold and markdownlint calls that MD036.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-18 17:14:11 +02:00
8 changed files with 74 additions and 6 deletions

View File

@@ -1,5 +1,12 @@
# Changelog # Changelog
## [2.0.1] - 2026-09-18
* A release no longer ends *CHANGELOG.md* on a blank line, which is MD012.
* The entry's separator only belongs there when another entry follows it.
* Affects the first entry a file receives, so it shows on an opening release.
* Four tests cover the placements in *_insert_after_h1*, one per branch.
## [2.0.0] - 2026-09-18 ## [2.0.0] - 2026-09-18
Breaking Breaking

View File

@@ -32,7 +32,7 @@
rec { rec {
pkgmgr = pyPkgs.buildPythonApplication { pkgmgr = pyPkgs.buildPythonApplication {
pname = "package-manager"; pname = "package-manager";
version = "2.0.0"; version = "2.0.1";
# Use the git repo as source # Use the git repo as source
src = ./.; src = ./.;

View File

@@ -1,7 +1,7 @@
# Maintainer: Kevin Veen-Birkenbach <info@veen.world> # Maintainer: Kevin Veen-Birkenbach <info@veen.world>
pkgname=package-manager pkgname=package-manager
pkgver=2.0.0 pkgver=2.0.1
pkgrel=1 pkgrel=1
pkgdesc="Local-flake wrapper for Kevin's package-manager (Nix-based)." pkgdesc="Local-flake wrapper for Kevin's package-manager (Nix-based)."
arch=('any') arch=('any')

View File

@@ -1,3 +1,12 @@
package-manager (2.0.1-1) unstable; urgency=medium
* A release no longer ends *CHANGELOG.md* on a blank line, which is MD012.
* The entry's separator only belongs there when another entry follows it.
* Affects the first entry a file receives, so it shows on an opening release.
* Four tests cover the placements in *_insert_after_h1*, one per branch.
-- Kevin Veen-Birkenbach <kevin@veen.world> Fri, 18 Sep 2026 17:15:35 +0200
package-manager (2.0.0-1) unstable; urgency=medium package-manager (2.0.0-1) unstable; urgency=medium
Breaking Breaking

View File

@@ -1,5 +1,5 @@
Name: package-manager Name: package-manager
Version: 2.0.0 Version: 2.0.1
Release: 1%{?dist} Release: 1%{?dist}
Summary: Wrapper that runs Kevin's package-manager via Nix flake Summary: Wrapper that runs Kevin's package-manager via Nix flake
@@ -74,6 +74,12 @@ echo ">>> package-manager removed. Nix itself was not removed."
/usr/lib/package-manager/ /usr/lib/package-manager/
%changelog %changelog
* Fri Sep 18 2026 Kevin Veen-Birkenbach <kevin@veen.world> - 2.0.1-1
- * A release no longer ends *CHANGELOG.md* on a blank line, which is MD012.
- * The entry's separator only belongs there when another entry follows it.
- * Affects the first entry a file receives, so it shows on an opening release.
- * Four tests cover the placements in *_insert_after_h1*, one per branch.
* Fri Sep 18 2026 Kevin Veen-Birkenbach <kevin@veen.world> - 2.0.0-1 * Fri Sep 18 2026 Kevin Veen-Birkenbach <kevin@veen.world> - 2.0.0-1
- Breaking - Breaking

View File

@@ -7,7 +7,7 @@ build-backend = "setuptools.build_meta"
[project] [project]
name = "kpmx" name = "kpmx"
version = "2.0.0" version = "2.0.1"
description = "Kevin's package-manager tool (pkgmgr)" description = "Kevin's package-manager tool (pkgmgr)"
readme = "README.md" readme = "README.md"
requires-python = ">=3.9" requires-python = ">=3.9"

View File

@@ -16,6 +16,11 @@ H1_RE = re.compile(r"^#\s+\S", re.MULTILINE)
H2_RE = re.compile(r"^##\s+\S", re.MULTILINE) H2_RE = re.compile(r"^##\s+\S", re.MULTILINE)
def _at_end_of_file(entry: str) -> str:
"""Return *entry* without the blank line that separates it from a next one."""
return entry.rstrip("\n") + "\n"
def _insert_after_h1(existing: str, entry: str) -> str: def _insert_after_h1(existing: str, entry: str) -> str:
"""Place *entry* after the H1 (and any intro prose), above the first H2. """Place *entry* after the H1 (and any intro prose), above the first H2.
@@ -26,7 +31,7 @@ def _insert_after_h1(existing: str, entry: str) -> str:
``## ``) is preserved: *entry* is prepended unchanged. ``## ``) is preserved: *entry* is prepended unchanged.
""" """
if not existing.strip(): if not existing.strip():
return f"# Changelog\n\n{entry}" return f"# Changelog\n\n{_at_end_of_file(entry)}"
if not H1_RE.search(existing): if not H1_RE.search(existing):
# Legacy layout: file starts with `## [version]` and has no H1. # Legacy layout: file starts with `## [version]` and has no H1.
@@ -43,7 +48,7 @@ def _insert_after_h1(existing: str, entry: str) -> str:
if existing.endswith("\n\n") if existing.endswith("\n\n")
else ("\n" if existing.endswith("\n") else "\n\n") else ("\n" if existing.endswith("\n") else "\n\n")
) )
return f"{existing}{suffix}{entry}" return f"{existing}{suffix}{_at_end_of_file(entry)}"
# Insert new entry just before the first H2. # Insert new entry just before the first H2.
head = existing[: h2_match.start()].rstrip("\n") + "\n\n" head = existing[: h2_match.start()].rstrip("\n") + "\n\n"

View File

@@ -0,0 +1,41 @@
from __future__ import annotations
import unittest
from pkgmgr.actions.release.files.changelog_md import _insert_after_h1
ENTRY = "## [1.0.0] - 2026-09-18\n\nOfficial Release\n\n"
class TestInsertAfterH1(unittest.TestCase):
def _assert_lint_clean(self, document: str) -> None:
self.assertNotIn(
"\n\n\n",
document,
f"MD012: multiple consecutive blank lines in\n{document!r}",
)
self.assertTrue(
document.endswith("\n") and not document.endswith("\n\n"),
f"MD012: blank line at end of file in\n{document!r}",
)
def test_an_empty_changelog_gets_one_trailing_newline(self) -> None:
self._assert_lint_clean(_insert_after_h1("", ENTRY))
def test_a_first_entry_under_a_bare_h1_gets_one_trailing_newline(self) -> None:
self._assert_lint_clean(_insert_after_h1("# Changelog\n", ENTRY))
def test_a_second_entry_stays_separated_from_the_first(self) -> None:
existing = "# Changelog\n\n## [0.9.0] - 2026-09-01\n\nOlder\n"
document = _insert_after_h1(existing, ENTRY)
self._assert_lint_clean(document)
self.assertIn("Official Release\n\n## [0.9.0]", document)
def test_a_legacy_headerless_changelog_gains_an_h1(self) -> None:
document = _insert_after_h1("## [0.9.0] - 2026-09-01\n\nOlder\n", ENTRY)
self._assert_lint_clean(document)
self.assertTrue(document.startswith("# Changelog\n\n## [1.0.0]"))
if __name__ == "__main__":
unittest.main()