Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a4b29b4d49 | ||
|
|
4e83779459 |
@@ -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
|
||||||
|
|||||||
@@ -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 = ./.;
|
||||||
|
|||||||
@@ -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')
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|||||||
41
tests/unit/pkgmgr/actions/release/test_changelog_md.py
Normal file
41
tests/unit/pkgmgr/actions/release/test_changelog_md.py
Normal 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()
|
||||||
Reference in New Issue
Block a user