Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c7b070a62b | ||
|
|
20cd174a1f | ||
|
|
ae4bfb5965 | ||
|
|
4f71680bb1 | ||
|
|
c78f5ac75e | ||
|
|
32e896fd09 | ||
|
|
d24eb0155c | ||
|
|
c6e866b2c9 |
No files matched your search
@@ -15,12 +15,12 @@ jobs:
|
||||
security-events: write
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v7
|
||||
|
||||
- name: Run hadolint (produce SARIF)
|
||||
id: hadolint
|
||||
continue-on-error: true
|
||||
uses: hadolint/hadolint-action@2332a7b74a6de0dda2e2221d575162eba76ba5e5
|
||||
uses: hadolint/hadolint-action@06be81baf89a55ffd0e24b8f04a4185738dd3387
|
||||
with:
|
||||
dockerfile: ./Dockerfile
|
||||
format: sarif
|
||||
|
||||
@@ -11,7 +11,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v7
|
||||
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v7
|
||||
|
||||
@@ -10,7 +10,7 @@ jobs:
|
||||
lint-shell:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v7
|
||||
- name: Install ShellCheck
|
||||
run: sudo apt-get update && sudo apt-get install -y shellcheck
|
||||
- name: Run ShellCheck
|
||||
|
||||
@@ -19,7 +19,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v7
|
||||
with:
|
||||
fetch-depth: 0
|
||||
fetch-tags: true # We need tags and main history for version comparison
|
||||
|
||||
@@ -16,7 +16,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout repository (with tags)
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v7
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
|
||||
@@ -23,7 +23,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v7
|
||||
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@v4
|
||||
|
||||
@@ -19,7 +19,7 @@ jobs:
|
||||
access-tokens = github.com=${{ secrets.GITHUB_TOKEN }}
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v7
|
||||
|
||||
- name: Show Docker version
|
||||
run: docker version
|
||||
|
||||
@@ -20,7 +20,7 @@ jobs:
|
||||
access-tokens = github.com=${{ secrets.GITHUB_TOKEN }}
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v7
|
||||
|
||||
- name: Show Docker version
|
||||
run: docker version
|
||||
|
||||
@@ -19,7 +19,7 @@ jobs:
|
||||
access-tokens = github.com=${{ secrets.GITHUB_TOKEN }}
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v7
|
||||
|
||||
- name: Show commit SHA
|
||||
run: git rev-parse HEAD
|
||||
|
||||
@@ -15,7 +15,7 @@ jobs:
|
||||
access-tokens = github.com=${{ secrets.GITHUB_TOKEN }}
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v7
|
||||
|
||||
- name: Show Docker version
|
||||
run: docker version
|
||||
|
||||
@@ -15,7 +15,7 @@ jobs:
|
||||
access-tokens = github.com=${{ secrets.GITHUB_TOKEN }}
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v7
|
||||
|
||||
- name: Show Docker version
|
||||
run: docker version
|
||||
|
||||
@@ -19,7 +19,7 @@ jobs:
|
||||
access-tokens = github.com=${{ secrets.GITHUB_TOKEN }}
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v7
|
||||
|
||||
- name: Show Docker version
|
||||
run: docker version
|
||||
|
||||
@@ -19,7 +19,7 @@ jobs:
|
||||
access-tokens = github.com=${{ secrets.GITHUB_TOKEN }}
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v7
|
||||
|
||||
- name: Show Docker version
|
||||
run: docker version
|
||||
|
||||
@@ -18,7 +18,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v7
|
||||
|
||||
- name: Install Nix
|
||||
uses: DeterminateSystems/nix-installer-action@v23
|
||||
|
||||
@@ -1,5 +1,31 @@
|
||||
# Changelog
|
||||
|
||||
## [2.0.4] - 2026-10-19
|
||||
|
||||
Fixed:
|
||||
|
||||
- CI: Python lint passes again on ruff 0.17 and still on 0.16
|
||||
- CI: actions bumped: *actions/checkout* v7, *hadolint-action* 3.5.0
|
||||
|
||||
Upgrade notes:
|
||||
|
||||
- 2.0.3 was never marked *stable* nor published to GHCR: its CI failed
|
||||
- Release: write list items with dash bullets when markdownlint-cli2 is absent
|
||||
|
||||
## [2.0.3] - 2026-10-18
|
||||
|
||||
Fixed:
|
||||
|
||||
- Release: the entry is linted inside the CHANGELOG.md it lands in
|
||||
- Release: a list marker that breaks the file's consistency is caught (MD004)
|
||||
- Release: findings the file already has are not blamed on the entry
|
||||
- Release: *--no-globs* keeps the target repo's other files out of the lint
|
||||
- Release: without markdownlint-cli2, list items must use dash bullets
|
||||
|
||||
Upgrade notes:
|
||||
|
||||
- Release: write list items with dash bullets when markdownlint-cli2 is absent
|
||||
|
||||
## [2.0.2] - 2026-10-01
|
||||
|
||||
Fixes a release flow that could reject every entry written for it.
|
||||
|
||||
@@ -32,7 +32,7 @@
|
||||
rec {
|
||||
pkgmgr = pyPkgs.buildPythonApplication {
|
||||
pname = "package-manager";
|
||||
version = "2.0.2";
|
||||
version = "2.0.4";
|
||||
|
||||
# Use the git repo as source
|
||||
src = ./.;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# Maintainer: Kevin Veen-Birkenbach <info@veen.world>
|
||||
|
||||
pkgname=package-manager
|
||||
pkgver=2.0.2
|
||||
pkgver=2.0.4
|
||||
pkgrel=1
|
||||
pkgdesc="Local-flake wrapper for Kevin's package-manager (Nix-based)."
|
||||
arch=('any')
|
||||
|
||||
@@ -1,3 +1,33 @@
|
||||
package-manager (2.0.4-1) unstable; urgency=medium
|
||||
|
||||
Fixed:
|
||||
|
||||
- CI: Python lint passes again on ruff 0.17 and still on 0.16
|
||||
- CI: actions bumped: *actions/checkout* v7, *hadolint-action* 3.5.0
|
||||
|
||||
Upgrade notes:
|
||||
|
||||
- 2.0.3 was never marked *stable* nor published to GHCR: its CI failed
|
||||
- Release: write list items with dash bullets when markdownlint-cli2 is absent
|
||||
|
||||
-- Kevin Veen-Birkenbach <kevin@veen.world> Mon, 19 Oct 2026 10:06:02 +0200
|
||||
|
||||
package-manager (2.0.3-1) unstable; urgency=medium
|
||||
|
||||
Fixed:
|
||||
|
||||
- Release: the entry is linted inside the CHANGELOG.md it lands in
|
||||
- Release: a list marker that breaks the file's consistency is caught (MD004)
|
||||
- Release: findings the file already has are not blamed on the entry
|
||||
- Release: *--no-globs* keeps the target repo's other files out of the lint
|
||||
- Release: without markdownlint-cli2, list items must use dash bullets
|
||||
|
||||
Upgrade notes:
|
||||
|
||||
- Release: write list items with dash bullets when markdownlint-cli2 is absent
|
||||
|
||||
-- Kevin Veen-Birkenbach <kevin@veen.world> Sun, 18 Oct 2026 23:33:52 +0200
|
||||
|
||||
package-manager (2.0.2-1) unstable; urgency=medium
|
||||
|
||||
Fixes a release flow that could reject every entry written for it.
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
Name: package-manager
|
||||
Version: 2.0.2
|
||||
Version: 2.0.4
|
||||
Release: 1%{?dist}
|
||||
Summary: Wrapper that runs Kevin's package-manager via Nix flake
|
||||
|
||||
@@ -74,6 +74,30 @@ echo ">>> package-manager removed. Nix itself was not removed."
|
||||
/usr/lib/package-manager/
|
||||
|
||||
%changelog
|
||||
* Mon Oct 19 2026 Kevin Veen-Birkenbach <kevin@veen.world> - 2.0.4-1
|
||||
- Fixed:
|
||||
|
||||
- - CI: Python lint passes again on ruff 0.17 and still on 0.16
|
||||
- - CI: actions bumped: *actions/checkout* v7, *hadolint-action* 3.5.0
|
||||
|
||||
- Upgrade notes:
|
||||
|
||||
- - 2.0.3 was never marked *stable* nor published to GHCR: its CI failed
|
||||
- - Release: write list items with dash bullets when markdownlint-cli2 is absent
|
||||
|
||||
* Sun Oct 18 2026 Kevin Veen-Birkenbach <kevin@veen.world> - 2.0.3-1
|
||||
- Fixed:
|
||||
|
||||
- - Release: the entry is linted inside the CHANGELOG.md it lands in
|
||||
- - Release: a list marker that breaks the file's consistency is caught (MD004)
|
||||
- - Release: findings the file already has are not blamed on the entry
|
||||
- - Release: *--no-globs* keeps the target repo's other files out of the lint
|
||||
- - Release: without markdownlint-cli2, list items must use dash bullets
|
||||
|
||||
- Upgrade notes:
|
||||
|
||||
- - Release: write list items with dash bullets when markdownlint-cli2 is absent
|
||||
|
||||
* Thu Oct 01 2026 Kevin Veen-Birkenbach <kevin@veen.world> - 2.0.2-1
|
||||
- Fixes a release flow that could reject every entry written for it.
|
||||
|
||||
|
||||
+1
-1
@@ -7,7 +7,7 @@ build-backend = "setuptools.build_meta"
|
||||
|
||||
[project]
|
||||
name = "kpmx"
|
||||
version = "2.0.2"
|
||||
version = "2.0.4"
|
||||
description = "Kevin's package-manager tool (pkgmgr)"
|
||||
readme = "README.md"
|
||||
requires-python = ">=3.9"
|
||||
|
||||
@@ -6,10 +6,13 @@ import re
|
||||
import shutil
|
||||
import subprocess
|
||||
import tempfile
|
||||
from collections import Counter
|
||||
|
||||
_HEADING = re.compile(r"^\s*(#{1,6})\s+(.*?)\s*#*\s*$")
|
||||
_INLINE_CODE = re.compile(r"`([^`\n]+)`")
|
||||
_FINDING = re.compile(r"\bMD\d{3}\b")
|
||||
_LOCATION = re.compile(r"^.*?:\d+(?::\d+)?\s+")
|
||||
_NON_DASH_ITEM = re.compile(r"^\s*[*+]\s")
|
||||
_MULTI_BLANK = re.compile(r"\n{3,}")
|
||||
_ENTRY_LEVEL = 3
|
||||
|
||||
@@ -59,6 +62,35 @@ def lint_changelog_entry(reference_path: str, entry: str) -> list[str]:
|
||||
return _builtin_lint(document)
|
||||
|
||||
|
||||
def lint_changelog_context(reference_path: str, existing: str, merged: str) -> list[str]:
|
||||
"""Return the findings *merged* has beyond those *existing* already has.
|
||||
|
||||
Args:
|
||||
reference_path: path of the target CHANGELOG.md; its directory supplies
|
||||
the markdownlint config.
|
||||
existing: the target file before the entry is inserted.
|
||||
merged: the target file with the entry inserted.
|
||||
|
||||
Returns:
|
||||
Findings of *merged* that *existing* does not produce. Empty when
|
||||
``markdownlint-cli2`` is unavailable.
|
||||
"""
|
||||
if not shutil.which("markdownlint-cli2"):
|
||||
return []
|
||||
known = Counter(
|
||||
_LOCATION.sub("", finding)
|
||||
for finding in (_markdownlint(reference_path, existing) if existing.strip() else [])
|
||||
)
|
||||
new: list[str] = []
|
||||
for finding in _markdownlint(reference_path, merged):
|
||||
key = _LOCATION.sub("", finding)
|
||||
if known[key]:
|
||||
known[key] -= 1
|
||||
else:
|
||||
new.append(finding)
|
||||
return new
|
||||
|
||||
|
||||
def _markdownlint(reference_path: str, document: str) -> list[str]:
|
||||
directory = os.path.dirname(os.path.abspath(reference_path)) or "."
|
||||
fd, tmp_path = tempfile.mkstemp(
|
||||
@@ -69,7 +101,7 @@ def _markdownlint(reference_path: str, document: str) -> list[str]:
|
||||
with os.fdopen(fd, "w", encoding="utf-8") as handle:
|
||||
handle.write(document)
|
||||
proc = subprocess.run(
|
||||
["markdownlint-cli2", name],
|
||||
["markdownlint-cli2", "--no-globs", name],
|
||||
cwd=directory,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
@@ -96,6 +128,8 @@ def _builtin_lint(document: str) -> list[str]:
|
||||
errors.append(f"line {number}: trailing whitespace (MD009)")
|
||||
if "`" in line:
|
||||
errors.append(f"line {number}: backtick is not allowed (use *italic*)")
|
||||
if _NON_DASH_ITEM.match(line):
|
||||
errors.append(f"line {number}: list items use '-' (MD004)")
|
||||
if "\n\n\n" in document:
|
||||
errors.append("multiple consecutive blank lines (MD012)")
|
||||
return errors
|
||||
@@ -3,10 +3,11 @@ from __future__ import annotations
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
from datetime import date
|
||||
from datetime import datetime
|
||||
|
||||
from .changelog_lint import (
|
||||
ChangelogLintError,
|
||||
lint_changelog_context,
|
||||
lint_changelog_entry,
|
||||
transform_changelog_message,
|
||||
)
|
||||
@@ -68,7 +69,7 @@ def update_changelog(
|
||||
missing) and above any existing release entries, so the result stays
|
||||
markdown-lint-clean (MD041 first-line-h1, MD012 no-multiple-blanks).
|
||||
"""
|
||||
today = date.today().isoformat() # noqa: DTZ011 - changelog dates are local calendar dates
|
||||
today = datetime.now().astimezone().date().isoformat()
|
||||
|
||||
def _entry_for(raw: str) -> tuple[str, str]:
|
||||
body = transform_changelog_message(raw).strip() or f"Release {new_version}"
|
||||
@@ -86,9 +87,22 @@ def update_changelog(
|
||||
except EOFError:
|
||||
print("[INFO] Re-opening the editor so you can fix the entry...")
|
||||
|
||||
changelog = ""
|
||||
if os.path.exists(changelog_path):
|
||||
try:
|
||||
with open(changelog_path, encoding="utf-8") as f:
|
||||
changelog = f.read()
|
||||
except (OSError, UnicodeDecodeError) as exc:
|
||||
print(f"[WARN] Could not read existing CHANGELOG.md: {exc}")
|
||||
|
||||
def _findings_for(entry: str) -> list[str]:
|
||||
return lint_changelog_entry(changelog_path, entry) or lint_changelog_context(
|
||||
changelog_path, changelog, _insert_after_h1(changelog, entry)
|
||||
)
|
||||
|
||||
if message is not None:
|
||||
body, entry = _entry_for(message)
|
||||
findings = lint_changelog_entry(changelog_path, entry)
|
||||
findings = _findings_for(entry)
|
||||
if findings:
|
||||
_print_findings(findings)
|
||||
raise ChangelogLintError(
|
||||
@@ -106,7 +120,7 @@ def update_changelog(
|
||||
)
|
||||
raw = _open_editor_for_changelog(attempt, rejected)
|
||||
body, entry = _entry_for(raw or f"Release {new_version}")
|
||||
findings = lint_changelog_entry(changelog_path, entry)
|
||||
findings = _findings_for(entry)
|
||||
if not findings:
|
||||
break
|
||||
_print_findings(findings)
|
||||
@@ -114,14 +128,6 @@ def update_changelog(
|
||||
rejected = findings
|
||||
_wait_for_reader()
|
||||
|
||||
changelog = ""
|
||||
if os.path.exists(changelog_path):
|
||||
try:
|
||||
with open(changelog_path, encoding="utf-8") as f:
|
||||
changelog = f.read()
|
||||
except (OSError, UnicodeDecodeError) as exc:
|
||||
print(f"[WARN] Could not read existing CHANGELOG.md: {exc}")
|
||||
|
||||
new_changelog = _insert_after_h1(changelog, entry)
|
||||
|
||||
print("\n================ CHANGELOG ENTRY ================")
|
||||
|
||||
@@ -1,11 +1,18 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import shutil
|
||||
import unittest
|
||||
from unittest.mock import patch
|
||||
|
||||
from pkgmgr.actions.release.files import changelog_lint
|
||||
from pkgmgr.actions.release.files.changelog_lint import (
|
||||
lint_changelog_context,
|
||||
lint_changelog_entry,
|
||||
transform_changelog_message,
|
||||
)
|
||||
from pkgmgr.actions.release.files.changelog_md import _insert_after_h1
|
||||
|
||||
_OLD_ASTERISK_FILE = "# Changelog\n\n## [1.0.0] - 2026-01-01\n\n* old bullet\n"
|
||||
|
||||
|
||||
class TestTransformChangelogMessage(unittest.TestCase):
|
||||
@@ -38,16 +45,42 @@ class TestTransformChangelogMessage(unittest.TestCase):
|
||||
|
||||
class TestLintChangelogEntry(unittest.TestCase):
|
||||
def test_clean_entry_has_no_findings(self) -> None:
|
||||
entry = "## [1.0.0] - 2026-01-01\n\n* a clean bullet\n\n"
|
||||
entry = "## [1.0.0] - 2026-01-01\n\n- a clean bullet\n\n"
|
||||
self.assertEqual(lint_changelog_entry("CHANGELOG.md", entry), [])
|
||||
|
||||
def test_a_structured_message_survives_its_own_transform(self) -> None:
|
||||
body = transform_changelog_message(
|
||||
"# Security\n\n* a bullet\n\n## Fixed\n\n* another bullet"
|
||||
"# Security\n\n- a bullet\n\n## Fixed\n\n- another bullet"
|
||||
)
|
||||
entry = f"## [1.0.0] - 2026-01-01\n\n{body}\n\n"
|
||||
self.assertEqual(lint_changelog_entry("CHANGELOG.md", entry), [])
|
||||
|
||||
def test_the_builtin_check_refuses_a_non_dash_list_marker(self) -> None:
|
||||
entry = "## [1.0.0] - 2026-01-01\n\n* a bullet\n\n"
|
||||
with patch.object(changelog_lint.shutil, "which", return_value=None):
|
||||
findings = lint_changelog_entry("CHANGELOG.md", entry)
|
||||
self.assertTrue(any("MD004" in finding for finding in findings), findings)
|
||||
|
||||
|
||||
@unittest.skipUnless(shutil.which("markdownlint-cli2"), "markdownlint-cli2 is not installed")
|
||||
class TestLintChangelogContext(unittest.TestCase):
|
||||
def test_a_marker_that_breaks_the_files_consistency_is_reported(self) -> None:
|
||||
entry = "## [1.1.0] - 2026-02-01\n\n- new bullet\n\n"
|
||||
merged = _insert_after_h1(_OLD_ASTERISK_FILE, entry)
|
||||
findings = lint_changelog_context("CHANGELOG.md", _OLD_ASTERISK_FILE, merged)
|
||||
self.assertTrue(any("MD004" in finding for finding in findings), findings)
|
||||
|
||||
def test_a_finding_the_file_already_has_is_not_blamed_on_the_entry(self) -> None:
|
||||
existing = "# Changelog\n\n## [1.0.0] - 2026-01-01\n\n* old bullet \n"
|
||||
entry = "## [1.1.0] - 2026-02-01\n\n* new bullet\n\n"
|
||||
merged = _insert_after_h1(existing, entry)
|
||||
self.assertEqual(lint_changelog_context("CHANGELOG.md", existing, merged), [])
|
||||
|
||||
def test_a_consistent_entry_has_no_findings(self) -> None:
|
||||
entry = "## [1.1.0] - 2026-02-01\n\n* new bullet\n\n"
|
||||
merged = _insert_after_h1(_OLD_ASTERISK_FILE, entry)
|
||||
self.assertEqual(lint_changelog_context("CHANGELOG.md", _OLD_ASTERISK_FILE, merged), [])
|
||||
|
||||
|
||||
if __name__ == "__main__": # pragma: no cover
|
||||
unittest.main()
|
||||
@@ -313,13 +313,13 @@ class TestUpdateChangelog(unittest.TestCase):
|
||||
def test_update_changelog_does_not_inject_a_leading_bullet(self) -> None:
|
||||
with tempfile.TemporaryDirectory() as tmpdir:
|
||||
path = os.path.join(tmpdir, "CHANGELOG.md")
|
||||
update_changelog(path, "1.2.3", message="* Provided bullet", preview=False)
|
||||
update_changelog(path, "1.2.3", message="- Provided bullet", preview=False)
|
||||
|
||||
with open(path, encoding="utf-8") as f:
|
||||
content = f.read()
|
||||
|
||||
self.assertIn("\n\n* Provided bullet\n", content)
|
||||
self.assertNotIn("* * Provided bullet", content)
|
||||
self.assertIn("\n\n- Provided bullet\n", content)
|
||||
self.assertNotIn("- - Provided bullet", content)
|
||||
|
||||
def test_update_changelog_transforms_heading_and_inline_code(self) -> None:
|
||||
with tempfile.TemporaryDirectory() as tmpdir:
|
||||
@@ -327,7 +327,7 @@ class TestUpdateChangelog(unittest.TestCase):
|
||||
update_changelog(
|
||||
path,
|
||||
"1.2.3",
|
||||
message="# Summary\n\n* uses `foo` tool",
|
||||
message="# Summary\n\n- uses `foo` tool",
|
||||
preview=False,
|
||||
)
|
||||
|
||||
|
||||
Reference in new issue
Block a user