Compare commits
22
Commits
v2.0.1
...
c7b070a62b
No files matched your search
@@ -15,12 +15,12 @@ jobs:
|
|||||||
security-events: write
|
security-events: write
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v7
|
||||||
|
|
||||||
- name: Run hadolint (produce SARIF)
|
- name: Run hadolint (produce SARIF)
|
||||||
id: hadolint
|
id: hadolint
|
||||||
continue-on-error: true
|
continue-on-error: true
|
||||||
uses: hadolint/hadolint-action@2332a7b74a6de0dda2e2221d575162eba76ba5e5
|
uses: hadolint/hadolint-action@06be81baf89a55ffd0e24b8f04a4185738dd3387
|
||||||
with:
|
with:
|
||||||
dockerfile: ./Dockerfile
|
dockerfile: ./Dockerfile
|
||||||
format: sarif
|
format: sarif
|
||||||
|
|||||||
@@ -11,10 +11,10 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v7
|
||||||
|
|
||||||
- name: Set up Python
|
- name: Set up Python
|
||||||
uses: actions/setup-python@v5
|
uses: actions/setup-python@v7
|
||||||
with:
|
with:
|
||||||
python-version: "3.12"
|
python-version: "3.12"
|
||||||
|
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ jobs:
|
|||||||
lint-shell:
|
lint-shell:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v7
|
||||||
- name: Install ShellCheck
|
- name: Install ShellCheck
|
||||||
run: sudo apt-get update && sudo apt-get install -y shellcheck
|
run: sudo apt-get update && sudo apt-get install -y shellcheck
|
||||||
- name: Run ShellCheck
|
- name: Run ShellCheck
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v7
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
fetch-tags: true # We need tags and main history for version comparison
|
fetch-tags: true # We need tags and main history for version comparison
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository (with tags)
|
- name: Checkout repository (with tags)
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v7
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
|
|
||||||
@@ -38,13 +38,13 @@ jobs:
|
|||||||
|
|
||||||
- name: Set up Docker Buildx
|
- name: Set up Docker Buildx
|
||||||
if: ${{ steps.info.outputs.should_publish == 'true' }}
|
if: ${{ steps.info.outputs.should_publish == 'true' }}
|
||||||
uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f
|
uses: docker/setup-buildx-action@594f3bf4285d9ea8dc53c9a0c9c4092420091003
|
||||||
with:
|
with:
|
||||||
use: true
|
use: true
|
||||||
|
|
||||||
- name: Login to GHCR
|
- name: Login to GHCR
|
||||||
if: ${{ steps.info.outputs.should_publish == 'true' }}
|
if: ${{ steps.info.outputs.should_publish == 'true' }}
|
||||||
uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9
|
uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f
|
||||||
with:
|
with:
|
||||||
registry: ghcr.io
|
registry: ghcr.io
|
||||||
username: ${{ github.actor }}
|
username: ${{ github.actor }}
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v7
|
||||||
|
|
||||||
- name: Initialize CodeQL
|
- name: Initialize CodeQL
|
||||||
uses: github/codeql-action/init@v4
|
uses: github/codeql-action/init@v4
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ jobs:
|
|||||||
access-tokens = github.com=${{ secrets.GITHUB_TOKEN }}
|
access-tokens = github.com=${{ secrets.GITHUB_TOKEN }}
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v7
|
||||||
|
|
||||||
- name: Show Docker version
|
- name: Show Docker version
|
||||||
run: docker version
|
run: docker version
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ jobs:
|
|||||||
access-tokens = github.com=${{ secrets.GITHUB_TOKEN }}
|
access-tokens = github.com=${{ secrets.GITHUB_TOKEN }}
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v7
|
||||||
|
|
||||||
- name: Show Docker version
|
- name: Show Docker version
|
||||||
run: docker version
|
run: docker version
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ jobs:
|
|||||||
access-tokens = github.com=${{ secrets.GITHUB_TOKEN }}
|
access-tokens = github.com=${{ secrets.GITHUB_TOKEN }}
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v7
|
||||||
|
|
||||||
- name: Show commit SHA
|
- name: Show commit SHA
|
||||||
run: git rev-parse HEAD
|
run: git rev-parse HEAD
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ jobs:
|
|||||||
access-tokens = github.com=${{ secrets.GITHUB_TOKEN }}
|
access-tokens = github.com=${{ secrets.GITHUB_TOKEN }}
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v7
|
||||||
|
|
||||||
- name: Show Docker version
|
- name: Show Docker version
|
||||||
run: docker version
|
run: docker version
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ jobs:
|
|||||||
access-tokens = github.com=${{ secrets.GITHUB_TOKEN }}
|
access-tokens = github.com=${{ secrets.GITHUB_TOKEN }}
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v7
|
||||||
|
|
||||||
- name: Show Docker version
|
- name: Show Docker version
|
||||||
run: docker version
|
run: docker version
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ jobs:
|
|||||||
access-tokens = github.com=${{ secrets.GITHUB_TOKEN }}
|
access-tokens = github.com=${{ secrets.GITHUB_TOKEN }}
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v7
|
||||||
|
|
||||||
- name: Show Docker version
|
- name: Show Docker version
|
||||||
run: docker version
|
run: docker version
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ jobs:
|
|||||||
access-tokens = github.com=${{ secrets.GITHUB_TOKEN }}
|
access-tokens = github.com=${{ secrets.GITHUB_TOKEN }}
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v7
|
||||||
|
|
||||||
- name: Show Docker version
|
- name: Show Docker version
|
||||||
run: docker version
|
run: docker version
|
||||||
|
|||||||
@@ -18,13 +18,13 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v7
|
||||||
|
|
||||||
- name: Install Nix
|
- name: Install Nix
|
||||||
uses: DeterminateSystems/nix-installer-action@v16
|
uses: DeterminateSystems/nix-installer-action@v23
|
||||||
|
|
||||||
- name: Update flake.lock and open a pull request
|
- name: Update flake.lock and open a pull request
|
||||||
uses: DeterminateSystems/update-flake-lock@v24
|
uses: DeterminateSystems/update-flake-lock@v29
|
||||||
with:
|
with:
|
||||||
pr-title: "chore(deps): update flake.lock"
|
pr-title: "chore(deps): update flake.lock"
|
||||||
pr-labels: |
|
pr-labels: |
|
||||||
|
|||||||
@@ -1,5 +1,65 @@
|
|||||||
# Changelog
|
# 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.
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- A heading in a changelog message became bold, which markdown-lint reads
|
||||||
|
back as a heading and refuses with MD036
|
||||||
|
- Since the editor reopens on failure, no structured entry could ever pass
|
||||||
|
- Headings now move below the release heading they are filed under
|
||||||
|
- The lint findings reached nobody: the editor took over the terminal in the
|
||||||
|
same breath they were printed
|
||||||
|
- They are held until Enter and repeated inside the editor, above the text
|
||||||
|
- The Makefile installer accepted install-* as proof of an install target but
|
||||||
|
always ran plain make install
|
||||||
|
- A repository with install-python and no install died on "No rule to make
|
||||||
|
target" instead of being skipped
|
||||||
|
|
||||||
|
### Tests
|
||||||
|
|
||||||
|
- transform and lint were covered separately, so a transform producing lint
|
||||||
|
errors went unseen; one test now runs the first through the second
|
||||||
|
- Three tests were checking behaviour the code had already left behind
|
||||||
|
|
||||||
|
### Dependencies
|
||||||
|
|
||||||
|
- docker/login-action 3.7.0 to 4.6.0, setup-buildx-action 3.12.0 to 4.4.0
|
||||||
|
- actions/setup-python 5 to 7
|
||||||
|
- DeterminateSystems nix-installer-action 16 to 23, update-flake-lock 24 to 29
|
||||||
|
|
||||||
|
### Upgrading
|
||||||
|
|
||||||
|
- Nothing to do; a changelog entry written with headings now keeps them
|
||||||
|
|
||||||
## [2.0.1] - 2026-09-18
|
## [2.0.1] - 2026-09-18
|
||||||
|
|
||||||
* A release no longer ends *CHANGELOG.md* on a blank line, which is MD012.
|
* A release no longer ends *CHANGELOG.md* on a blank line, which is MD012.
|
||||||
|
|||||||
@@ -32,7 +32,7 @@
|
|||||||
rec {
|
rec {
|
||||||
pkgmgr = pyPkgs.buildPythonApplication {
|
pkgmgr = pyPkgs.buildPythonApplication {
|
||||||
pname = "package-manager";
|
pname = "package-manager";
|
||||||
version = "2.0.1";
|
version = "2.0.4";
|
||||||
|
|
||||||
# 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.1
|
pkgver=2.0.4
|
||||||
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,69 @@
|
|||||||
|
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.
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- A heading in a changelog message became bold, which markdown-lint reads
|
||||||
|
back as a heading and refuses with MD036
|
||||||
|
- Since the editor reopens on failure, no structured entry could ever pass
|
||||||
|
- Headings now move below the release heading they are filed under
|
||||||
|
- The lint findings reached nobody: the editor took over the terminal in the
|
||||||
|
same breath they were printed
|
||||||
|
- They are held until Enter and repeated inside the editor, above the text
|
||||||
|
- The Makefile installer accepted install-* as proof of an install target but
|
||||||
|
always ran plain make install
|
||||||
|
- A repository with install-python and no install died on "No rule to make
|
||||||
|
target" instead of being skipped
|
||||||
|
|
||||||
|
### Tests
|
||||||
|
|
||||||
|
- transform and lint were covered separately, so a transform producing lint
|
||||||
|
errors went unseen; one test now runs the first through the second
|
||||||
|
- Three tests were checking behaviour the code had already left behind
|
||||||
|
|
||||||
|
### Dependencies
|
||||||
|
|
||||||
|
- docker/login-action 3.7.0 to 4.6.0, setup-buildx-action 3.12.0 to 4.4.0
|
||||||
|
- actions/setup-python 5 to 7
|
||||||
|
- DeterminateSystems nix-installer-action 16 to 23, update-flake-lock 24 to 29
|
||||||
|
|
||||||
|
### Upgrading
|
||||||
|
|
||||||
|
- Nothing to do; a changelog entry written with headings now keeps them
|
||||||
|
|
||||||
|
-- Kevin Veen-Birkenbach <kevin@veen.world> Thu, 01 Oct 2026 17:47:12 +0200
|
||||||
|
|
||||||
package-manager (2.0.1-1) unstable; urgency=medium
|
package-manager (2.0.1-1) unstable; urgency=medium
|
||||||
|
|
||||||
* A release no longer ends *CHANGELOG.md* on a blank line, which is MD012.
|
* A release no longer ends *CHANGELOG.md* on a blank line, which is MD012.
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
Name: package-manager
|
Name: package-manager
|
||||||
Version: 2.0.1
|
Version: 2.0.4
|
||||||
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,63 @@ echo ">>> package-manager removed. Nix itself was not removed."
|
|||||||
/usr/lib/package-manager/
|
/usr/lib/package-manager/
|
||||||
|
|
||||||
%changelog
|
%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.
|
||||||
|
|
||||||
|
- ### Fixed
|
||||||
|
|
||||||
|
- - A heading in a changelog message became bold, which markdown-lint reads
|
||||||
|
- back as a heading and refuses with MD036
|
||||||
|
- - Since the editor reopens on failure, no structured entry could ever pass
|
||||||
|
- - Headings now move below the release heading they are filed under
|
||||||
|
- - The lint findings reached nobody: the editor took over the terminal in the
|
||||||
|
- same breath they were printed
|
||||||
|
- - They are held until Enter and repeated inside the editor, above the text
|
||||||
|
- - The Makefile installer accepted install-* as proof of an install target but
|
||||||
|
- always ran plain make install
|
||||||
|
- - A repository with install-python and no install died on "No rule to make
|
||||||
|
- target" instead of being skipped
|
||||||
|
|
||||||
|
- ### Tests
|
||||||
|
|
||||||
|
- - transform and lint were covered separately, so a transform producing lint
|
||||||
|
- errors went unseen; one test now runs the first through the second
|
||||||
|
- - Three tests were checking behaviour the code had already left behind
|
||||||
|
|
||||||
|
- ### Dependencies
|
||||||
|
|
||||||
|
- - docker/login-action 3.7.0 to 4.6.0, setup-buildx-action 3.12.0 to 4.4.0
|
||||||
|
- - actions/setup-python 5 to 7
|
||||||
|
- - DeterminateSystems nix-installer-action 16 to 23, update-flake-lock 24 to 29
|
||||||
|
|
||||||
|
- ### Upgrading
|
||||||
|
|
||||||
|
- - Nothing to do; a changelog entry written with headings now keeps them
|
||||||
|
|
||||||
* Fri Sep 18 2026 Kevin Veen-Birkenbach <kevin@veen.world> - 2.0.1-1
|
* 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.
|
- * 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.
|
- * The entry's separator only belongs there when another entry follows it.
|
||||||
|
|||||||
+1
-1
@@ -7,7 +7,7 @@ build-backend = "setuptools.build_meta"
|
|||||||
|
|
||||||
[project]
|
[project]
|
||||||
name = "kpmx"
|
name = "kpmx"
|
||||||
version = "2.0.1"
|
version = "2.0.4"
|
||||||
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"
|
||||||
|
|||||||
@@ -31,9 +31,7 @@ class MakefileInstaller(BaseInstaller):
|
|||||||
except OSError:
|
except OSError:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
if re.search(r"^install\s*:", content, flags=re.MULTILINE):
|
return bool(re.search(r"^install\s*:", content, flags=re.MULTILINE))
|
||||||
return True
|
|
||||||
return bool(re.search(r"^install-[a-zA-Z0-9_-]*\s*:", content, flags=re.MULTILINE))
|
|
||||||
|
|
||||||
def run(self, ctx: RepoContext) -> None:
|
def run(self, ctx: RepoContext) -> None:
|
||||||
makefile_path = os.path.join(ctx.repo_dir, self.MAKEFILE_NAME)
|
makefile_path = os.path.join(ctx.repo_dir, self.MAKEFILE_NAME)
|
||||||
|
|||||||
@@ -6,11 +6,15 @@ import re
|
|||||||
import shutil
|
import shutil
|
||||||
import subprocess
|
import subprocess
|
||||||
import tempfile
|
import tempfile
|
||||||
|
from collections import Counter
|
||||||
|
|
||||||
_HEADING = re.compile(r"^\s*#{1,6}\s+(.*?)\s*#*\s*$")
|
_HEADING = re.compile(r"^\s*(#{1,6})\s+(.*?)\s*#*\s*$")
|
||||||
_INLINE_CODE = re.compile(r"`([^`\n]+)`")
|
_INLINE_CODE = re.compile(r"`([^`\n]+)`")
|
||||||
_FINDING = re.compile(r"\bMD\d{3}\b")
|
_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,}")
|
_MULTI_BLANK = re.compile(r"\n{3,}")
|
||||||
|
_ENTRY_LEVEL = 3
|
||||||
|
|
||||||
|
|
||||||
class ChangelogLintError(RuntimeError):
|
class ChangelogLintError(RuntimeError):
|
||||||
@@ -20,10 +24,10 @@ class ChangelogLintError(RuntimeError):
|
|||||||
def transform_changelog_message(text: str) -> str:
|
def transform_changelog_message(text: str) -> str:
|
||||||
"""Normalise a free-form release message into the changelog house style.
|
"""Normalise a free-form release message into the changelog house style.
|
||||||
|
|
||||||
A leading ``#`` heading becomes a bold line of its own (markdown
|
Headings are pushed below the ``## [version]`` line the entry is filed
|
||||||
headings inside an entry body would collide with the ``## [version]``
|
under, so they nest instead of competing with it. Bolding them instead
|
||||||
structure), and inline ``code`` spans become ``*italic*`` so the entry
|
would read as a heading to markdown-lint and fail MD036. Inline ``code``
|
||||||
stays free of backticks.
|
spans become ``*italic*`` so the entry stays free of backticks.
|
||||||
"""
|
"""
|
||||||
text = _INLINE_CODE.sub(r"*\1*", text)
|
text = _INLINE_CODE.sub(r"*\1*", text)
|
||||||
|
|
||||||
@@ -31,9 +35,10 @@ def transform_changelog_message(text: str) -> str:
|
|||||||
for line in text.split("\n"):
|
for line in text.split("\n"):
|
||||||
heading = _HEADING.match(line)
|
heading = _HEADING.match(line)
|
||||||
if heading:
|
if heading:
|
||||||
|
level = max(len(heading.group(1)), _ENTRY_LEVEL)
|
||||||
if out and out[-1].strip():
|
if out and out[-1].strip():
|
||||||
out.append("")
|
out.append("")
|
||||||
out.append(f"**{heading.group(1).strip()}**")
|
out.append(f"{'#' * level} {heading.group(2).strip()}")
|
||||||
out.append("")
|
out.append("")
|
||||||
else:
|
else:
|
||||||
out.append(line)
|
out.append(line)
|
||||||
@@ -57,6 +62,35 @@ def lint_changelog_entry(reference_path: str, entry: str) -> list[str]:
|
|||||||
return _builtin_lint(document)
|
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]:
|
def _markdownlint(reference_path: str, document: str) -> list[str]:
|
||||||
directory = os.path.dirname(os.path.abspath(reference_path)) or "."
|
directory = os.path.dirname(os.path.abspath(reference_path)) or "."
|
||||||
fd, tmp_path = tempfile.mkstemp(
|
fd, tmp_path = tempfile.mkstemp(
|
||||||
@@ -67,7 +101,7 @@ def _markdownlint(reference_path: str, document: str) -> list[str]:
|
|||||||
with os.fdopen(fd, "w", encoding="utf-8") as handle:
|
with os.fdopen(fd, "w", encoding="utf-8") as handle:
|
||||||
handle.write(document)
|
handle.write(document)
|
||||||
proc = subprocess.run(
|
proc = subprocess.run(
|
||||||
["markdownlint-cli2", name],
|
["markdownlint-cli2", "--no-globs", name],
|
||||||
cwd=directory,
|
cwd=directory,
|
||||||
capture_output=True,
|
capture_output=True,
|
||||||
text=True,
|
text=True,
|
||||||
@@ -94,6 +128,8 @@ def _builtin_lint(document: str) -> list[str]:
|
|||||||
errors.append(f"line {number}: trailing whitespace (MD009)")
|
errors.append(f"line {number}: trailing whitespace (MD009)")
|
||||||
if "`" in line:
|
if "`" in line:
|
||||||
errors.append(f"line {number}: backtick is not allowed (use *italic*)")
|
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:
|
if "\n\n\n" in document:
|
||||||
errors.append("multiple consecutive blank lines (MD012)")
|
errors.append("multiple consecutive blank lines (MD012)")
|
||||||
return errors
|
return errors
|
||||||
@@ -3,10 +3,11 @@ from __future__ import annotations
|
|||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
import sys
|
import sys
|
||||||
from datetime import date
|
from datetime import datetime
|
||||||
|
|
||||||
from .changelog_lint import (
|
from .changelog_lint import (
|
||||||
ChangelogLintError,
|
ChangelogLintError,
|
||||||
|
lint_changelog_context,
|
||||||
lint_changelog_entry,
|
lint_changelog_entry,
|
||||||
transform_changelog_message,
|
transform_changelog_message,
|
||||||
)
|
)
|
||||||
@@ -68,7 +69,7 @@ def update_changelog(
|
|||||||
missing) and above any existing release entries, so the result stays
|
missing) and above any existing release entries, so the result stays
|
||||||
markdown-lint-clean (MD041 first-line-h1, MD012 no-multiple-blanks).
|
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]:
|
def _entry_for(raw: str) -> tuple[str, str]:
|
||||||
body = transform_changelog_message(raw).strip() or f"Release {new_version}"
|
body = transform_changelog_message(raw).strip() or f"Release {new_version}"
|
||||||
@@ -80,30 +81,10 @@ def update_changelog(
|
|||||||
print(f" - {finding}")
|
print(f" - {finding}")
|
||||||
print()
|
print()
|
||||||
|
|
||||||
if message is not None:
|
def _wait_for_reader() -> None:
|
||||||
body, entry = _entry_for(message)
|
try:
|
||||||
findings = lint_changelog_entry(changelog_path, entry)
|
input("[INFO] Press Enter to re-open the editor and fix the entry...")
|
||||||
if findings:
|
except EOFError:
|
||||||
_print_findings(findings)
|
|
||||||
raise ChangelogLintError(
|
|
||||||
"Provided changelog message is not markdown-lint clean."
|
|
||||||
)
|
|
||||||
elif preview or not sys.stdin.isatty():
|
|
||||||
body, entry = _entry_for(message or f"Release {new_version}")
|
|
||||||
else:
|
|
||||||
attempt: str | None = None
|
|
||||||
while True:
|
|
||||||
print(
|
|
||||||
"\n[INFO] Provide the changelog entry — a leading '#' becomes "
|
|
||||||
"bold, `code` becomes italic.\n"
|
|
||||||
)
|
|
||||||
raw = _open_editor_for_changelog(attempt)
|
|
||||||
body, entry = _entry_for(raw or f"Release {new_version}")
|
|
||||||
findings = lint_changelog_entry(changelog_path, entry)
|
|
||||||
if not findings:
|
|
||||||
break
|
|
||||||
_print_findings(findings)
|
|
||||||
attempt = body
|
|
||||||
print("[INFO] Re-opening the editor so you can fix the entry...")
|
print("[INFO] Re-opening the editor so you can fix the entry...")
|
||||||
|
|
||||||
changelog = ""
|
changelog = ""
|
||||||
@@ -114,6 +95,39 @@ def update_changelog(
|
|||||||
except (OSError, UnicodeDecodeError) as exc:
|
except (OSError, UnicodeDecodeError) as exc:
|
||||||
print(f"[WARN] Could not read existing CHANGELOG.md: {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 = _findings_for(entry)
|
||||||
|
if findings:
|
||||||
|
_print_findings(findings)
|
||||||
|
raise ChangelogLintError(
|
||||||
|
"Provided changelog message is not markdown-lint clean."
|
||||||
|
)
|
||||||
|
elif preview or not sys.stdin.isatty():
|
||||||
|
body, entry = _entry_for(message or f"Release {new_version}")
|
||||||
|
else:
|
||||||
|
attempt: str | None = None
|
||||||
|
rejected: list[str] | None = None
|
||||||
|
while True:
|
||||||
|
print(
|
||||||
|
"\n[INFO] Provide the changelog entry - a leading '#' becomes "
|
||||||
|
"a sub-heading, `code` becomes italic.\n"
|
||||||
|
)
|
||||||
|
raw = _open_editor_for_changelog(attempt, rejected)
|
||||||
|
body, entry = _entry_for(raw or f"Release {new_version}")
|
||||||
|
findings = _findings_for(entry)
|
||||||
|
if not findings:
|
||||||
|
break
|
||||||
|
_print_findings(findings)
|
||||||
|
attempt = body
|
||||||
|
rejected = findings
|
||||||
|
_wait_for_reader()
|
||||||
|
|
||||||
new_changelog = _insert_after_h1(changelog, entry)
|
new_changelog = _insert_after_h1(changelog, entry)
|
||||||
|
|
||||||
print("\n================ CHANGELOG ENTRY ================")
|
print("\n================ CHANGELOG ENTRY ================")
|
||||||
|
|||||||
@@ -6,7 +6,14 @@ import subprocess
|
|||||||
import tempfile
|
import tempfile
|
||||||
|
|
||||||
|
|
||||||
def _open_editor_for_changelog(initial_message: str | None = None) -> str:
|
def _open_editor_for_changelog(
|
||||||
|
initial_message: str | None = None,
|
||||||
|
findings: list[str] | None = None,
|
||||||
|
) -> str:
|
||||||
|
"""Args:
|
||||||
|
initial_message: the previous attempt, pre-loaded for editing.
|
||||||
|
findings: why that attempt was rejected, shown above it.
|
||||||
|
"""
|
||||||
editor = os.environ.get("EDITOR", "nano")
|
editor = os.environ.get("EDITOR", "nano")
|
||||||
|
|
||||||
with tempfile.NamedTemporaryFile(
|
with tempfile.NamedTemporaryFile(
|
||||||
@@ -18,9 +25,15 @@ def _open_editor_for_changelog(initial_message: str | None = None) -> str:
|
|||||||
tmp.write(
|
tmp.write(
|
||||||
"; Write the changelog entry for this release.\n"
|
"; Write the changelog entry for this release.\n"
|
||||||
"; Lines starting with ';' are ignored.\n"
|
"; Lines starting with ';' are ignored.\n"
|
||||||
"; A leading '#' becomes bold; `code` becomes italic.\n"
|
"; A leading '#' becomes a sub-heading; `code` becomes italic.\n"
|
||||||
"; Empty result will fall back to a generic message.\n\n"
|
"; Empty result will fall back to a generic message.\n"
|
||||||
)
|
)
|
||||||
|
if findings:
|
||||||
|
tmp.write(";\n; markdown-lint rejected the previous entry:\n")
|
||||||
|
for finding in findings:
|
||||||
|
for line in str(finding).splitlines() or [""]:
|
||||||
|
tmp.write(f"; {line}\n")
|
||||||
|
tmp.write("\n")
|
||||||
if initial_message:
|
if initial_message:
|
||||||
tmp.write(initial_message.strip() + "\n")
|
tmp.write(initial_message.strip() + "\n")
|
||||||
tmp.flush()
|
tmp.flush()
|
||||||
|
|||||||
@@ -72,6 +72,8 @@ class TestDebianControlInstaller(unittest.TestCase):
|
|||||||
return "/usr/bin/dpkg-buildpackage"
|
return "/usr/bin/dpkg-buildpackage"
|
||||||
if name == "apt-get":
|
if name == "apt-get":
|
||||||
return "/usr/bin/apt-get"
|
return "/usr/bin/apt-get"
|
||||||
|
if name == "sudo":
|
||||||
|
return "/usr/bin/sudo"
|
||||||
return None
|
return None
|
||||||
|
|
||||||
mock_which.side_effect = which_side_effect
|
mock_which.side_effect = which_side_effect
|
||||||
|
|||||||
@@ -81,6 +81,19 @@ class TestMakefileInstaller(unittest.TestCase):
|
|||||||
)
|
)
|
||||||
mock_run_command.assert_not_called()
|
mock_run_command.assert_not_called()
|
||||||
|
|
||||||
|
@patch("pkgmgr.actions.install.installers.makefile.run_command")
|
||||||
|
@patch(
|
||||||
|
"builtins.open",
|
||||||
|
new_callable=mock_open,
|
||||||
|
read_data="install-python:\n\tpip install -r requirements.txt\n",
|
||||||
|
)
|
||||||
|
@patch("os.path.exists", return_value=True)
|
||||||
|
def test_run_skips_a_makefile_that_only_has_a_prefixed_target(
|
||||||
|
self, mock_exists, mock_file, mock_run_command
|
||||||
|
):
|
||||||
|
self.installer.run(self.ctx)
|
||||||
|
mock_run_command.assert_not_called()
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
unittest.main()
|
unittest.main()
|
||||||
@@ -1,23 +1,31 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import shutil
|
||||||
import unittest
|
import unittest
|
||||||
|
from unittest.mock import patch
|
||||||
|
|
||||||
|
from pkgmgr.actions.release.files import changelog_lint
|
||||||
from pkgmgr.actions.release.files.changelog_lint import (
|
from pkgmgr.actions.release.files.changelog_lint import (
|
||||||
|
lint_changelog_context,
|
||||||
lint_changelog_entry,
|
lint_changelog_entry,
|
||||||
transform_changelog_message,
|
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):
|
class TestTransformChangelogMessage(unittest.TestCase):
|
||||||
def test_heading_becomes_bold(self) -> None:
|
def test_heading_is_pushed_below_the_release_heading(self) -> None:
|
||||||
out = transform_changelog_message("# Title\n\nbody")
|
out = transform_changelog_message("# Title\n\nbody")
|
||||||
self.assertIn("**Title**", out)
|
self.assertIn("### Title", out)
|
||||||
self.assertNotIn("# Title", out)
|
self.assertNotIn("**Title**", out)
|
||||||
|
|
||||||
def test_multi_hash_heading_becomes_bold(self) -> None:
|
def test_a_heading_already_deep_enough_keeps_its_level(self) -> None:
|
||||||
self.assertEqual(
|
self.assertEqual(transform_changelog_message("### Sub heading"), "### Sub heading")
|
||||||
transform_changelog_message("### Sub heading"), "**Sub heading**"
|
|
||||||
)
|
def test_a_deeper_heading_is_left_alone(self) -> None:
|
||||||
|
self.assertEqual(transform_changelog_message("#### Deeper"), "#### Deeper")
|
||||||
|
|
||||||
def test_inline_code_becomes_italic(self) -> None:
|
def test_inline_code_becomes_italic(self) -> None:
|
||||||
out = transform_changelog_message("use `pkgmgr release` now")
|
out = transform_changelog_message("use `pkgmgr release` now")
|
||||||
@@ -27,18 +35,52 @@ class TestTransformChangelogMessage(unittest.TestCase):
|
|||||||
def test_plain_message_is_unchanged(self) -> None:
|
def test_plain_message_is_unchanged(self) -> None:
|
||||||
self.assertEqual(transform_changelog_message("just text"), "just text")
|
self.assertEqual(transform_changelog_message("just text"), "just text")
|
||||||
|
|
||||||
def test_no_heading_or_backtick_survives(self) -> None:
|
def test_no_backtick_survives_and_no_heading_competes(self) -> None:
|
||||||
out = transform_changelog_message("# Heading\n\n* item with `code`")
|
out = transform_changelog_message("# Heading\n\n* item with `code`")
|
||||||
self.assertNotIn("`", out)
|
self.assertNotIn("`", out)
|
||||||
for line in out.split("\n"):
|
for line in out.split("\n"):
|
||||||
self.assertFalse(line.lstrip().startswith("#"))
|
if line.lstrip().startswith("#"):
|
||||||
|
self.assertTrue(line.startswith("###"), line)
|
||||||
|
|
||||||
|
|
||||||
class TestLintChangelogEntry(unittest.TestCase):
|
class TestLintChangelogEntry(unittest.TestCase):
|
||||||
def test_clean_entry_has_no_findings(self) -> None:
|
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), [])
|
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"
|
||||||
|
)
|
||||||
|
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
|
if __name__ == "__main__": # pragma: no cover
|
||||||
unittest.main()
|
unittest.main()
|
||||||
@@ -0,0 +1,60 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import unittest
|
||||||
|
from unittest import mock
|
||||||
|
|
||||||
|
from pkgmgr.actions.release.files.editor import _open_editor_for_changelog
|
||||||
|
|
||||||
|
|
||||||
|
class TestOpenEditorForChangelog(unittest.TestCase):
|
||||||
|
def _buffer(self, **kwargs: object) -> str:
|
||||||
|
"""Returns: what the editor was handed, with the editor itself a no-op."""
|
||||||
|
seen: dict[str, str] = {}
|
||||||
|
|
||||||
|
def record(argv: list[str]) -> int:
|
||||||
|
with open(argv[1], encoding="utf-8") as handle:
|
||||||
|
seen["text"] = handle.read()
|
||||||
|
return 0
|
||||||
|
|
||||||
|
with mock.patch(
|
||||||
|
"pkgmgr.actions.release.files.editor.subprocess.call", side_effect=record
|
||||||
|
):
|
||||||
|
_open_editor_for_changelog(**kwargs)
|
||||||
|
return seen["text"]
|
||||||
|
|
||||||
|
def test_the_rejection_reaches_the_editor_the_entry_is_fixed_in(self) -> None:
|
||||||
|
text = self._buffer(
|
||||||
|
initial_message="### Security\n\n- a bullet",
|
||||||
|
findings=["changelog entry:3 error MD036/no-emphasis-as-heading"],
|
||||||
|
)
|
||||||
|
self.assertIn("MD036/no-emphasis-as-heading", text)
|
||||||
|
self.assertIn("### Security", text)
|
||||||
|
|
||||||
|
def test_every_finding_line_is_commented_out(self) -> None:
|
||||||
|
text = self._buffer(findings=["first line\nsecond line"])
|
||||||
|
for line in text.splitlines():
|
||||||
|
if "line" in line and "ignored" not in line:
|
||||||
|
self.assertTrue(line.startswith(";"), line)
|
||||||
|
|
||||||
|
def test_a_finding_never_lands_in_the_entry(self) -> None:
|
||||||
|
seen: dict[str, str] = {}
|
||||||
|
|
||||||
|
def rewrite(argv: list[str]) -> int:
|
||||||
|
with open(argv[1], encoding="utf-8") as handle:
|
||||||
|
seen["text"] = handle.read()
|
||||||
|
return 0
|
||||||
|
|
||||||
|
with mock.patch(
|
||||||
|
"pkgmgr.actions.release.files.editor.subprocess.call", side_effect=rewrite
|
||||||
|
):
|
||||||
|
kept = _open_editor_for_changelog(
|
||||||
|
initial_message="- a bullet", findings=["MD036 somewhere"]
|
||||||
|
)
|
||||||
|
self.assertEqual(kept, "- a bullet")
|
||||||
|
|
||||||
|
def test_without_findings_the_header_stays_as_it_was(self) -> None:
|
||||||
|
self.assertNotIn("markdown-lint rejected", self._buffer())
|
||||||
|
|
||||||
|
|
||||||
|
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:
|
def test_update_changelog_does_not_inject_a_leading_bullet(self) -> None:
|
||||||
with tempfile.TemporaryDirectory() as tmpdir:
|
with tempfile.TemporaryDirectory() as tmpdir:
|
||||||
path = os.path.join(tmpdir, "CHANGELOG.md")
|
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:
|
with open(path, encoding="utf-8") as f:
|
||||||
content = f.read()
|
content = f.read()
|
||||||
|
|
||||||
self.assertIn("\n\n* Provided bullet\n", content)
|
self.assertIn("\n\n- Provided bullet\n", content)
|
||||||
self.assertNotIn("* * Provided bullet", content)
|
self.assertNotIn("- - Provided bullet", content)
|
||||||
|
|
||||||
def test_update_changelog_transforms_heading_and_inline_code(self) -> None:
|
def test_update_changelog_transforms_heading_and_inline_code(self) -> None:
|
||||||
with tempfile.TemporaryDirectory() as tmpdir:
|
with tempfile.TemporaryDirectory() as tmpdir:
|
||||||
@@ -327,16 +327,17 @@ class TestUpdateChangelog(unittest.TestCase):
|
|||||||
update_changelog(
|
update_changelog(
|
||||||
path,
|
path,
|
||||||
"1.2.3",
|
"1.2.3",
|
||||||
message="# Summary\n\n* uses `foo` tool",
|
message="# Summary\n\n- uses `foo` tool",
|
||||||
preview=False,
|
preview=False,
|
||||||
)
|
)
|
||||||
|
|
||||||
with open(path, encoding="utf-8") as f:
|
with open(path, encoding="utf-8") as f:
|
||||||
content = f.read()
|
content = f.read()
|
||||||
|
|
||||||
self.assertIn("**Summary**", content)
|
self.assertIn("### Summary", content)
|
||||||
self.assertIn("*foo*", content)
|
self.assertIn("*foo*", content)
|
||||||
self.assertNotIn("# Summary", content)
|
self.assertNotIn("\n# Summary", content)
|
||||||
|
self.assertNotIn("\n## Summary", content)
|
||||||
self.assertNotIn("`foo`", content)
|
self.assertNotIn("`foo`", content)
|
||||||
|
|
||||||
def test_update_changelog_preview_does_not_write(self) -> None:
|
def test_update_changelog_preview_does_not_write(self) -> None:
|
||||||
|
|||||||
@@ -29,22 +29,19 @@ class FindDefaultsSourceDirTests(unittest.TestCase):
|
|||||||
|
|
||||||
self.assertEqual(Path(found).resolve(), (pkg_root / "config").resolve())
|
self.assertEqual(Path(found).resolve(), (pkg_root / "config").resolve())
|
||||||
|
|
||||||
def test_falls_back_to_project_root_config(self):
|
def test_a_config_dir_beside_the_package_is_not_taken(self):
|
||||||
with tempfile.TemporaryDirectory() as td:
|
with tempfile.TemporaryDirectory() as td:
|
||||||
root = Path(td)
|
root = Path(td)
|
||||||
pkg_root = root / "site-packages" / "pkgmgr"
|
pkg_root = root / "site-packages" / "pkgmgr"
|
||||||
pkg_root.mkdir(parents=True)
|
pkg_root.mkdir(parents=True)
|
||||||
|
|
||||||
# only project_root config exists
|
|
||||||
(pkg_root.parent / "config").mkdir(parents=True)
|
(pkg_root.parent / "config").mkdir(parents=True)
|
||||||
|
|
||||||
fake_pkgmgr = types.SimpleNamespace(__file__=str(pkg_root / "__init__.py"))
|
fake_pkgmgr = types.SimpleNamespace(__file__=str(pkg_root / "__init__.py"))
|
||||||
with patch.dict(sys.modules, {"pkgmgr": fake_pkgmgr}):
|
with patch.dict(sys.modules, {"pkgmgr": fake_pkgmgr}):
|
||||||
found = config_cmd._find_defaults_source_dir()
|
found = config_cmd._find_defaults_source_dir()
|
||||||
|
|
||||||
self.assertEqual(
|
self.assertIsNone(found)
|
||||||
Path(found).resolve(), (pkg_root.parent / "config").resolve()
|
|
||||||
)
|
|
||||||
|
|
||||||
def test_returns_none_when_no_config_dirs_exist(self):
|
def test_returns_none_when_no_config_dirs_exist(self):
|
||||||
with tempfile.TemporaryDirectory() as td:
|
with tempfile.TemporaryDirectory() as td:
|
||||||
@@ -88,9 +85,8 @@ class UpdateDefaultConfigsTests(unittest.TestCase):
|
|||||||
self.assertTrue((dest_cfg_dir / "a.yaml").is_file())
|
self.assertTrue((dest_cfg_dir / "a.yaml").is_file())
|
||||||
self.assertTrue((dest_cfg_dir / "b.yml").is_file())
|
self.assertTrue((dest_cfg_dir / "b.yml").is_file())
|
||||||
self.assertFalse(
|
self.assertFalse(
|
||||||
(dest_cfg_dir / "config.yaml")
|
(dest_cfg_dir / "config.yaml").exists(),
|
||||||
.read_text(encoding="utf-8")
|
"the source config.yaml must not be copied over the user's own",
|
||||||
.startswith("should_not_copy")
|
|
||||||
)
|
)
|
||||||
|
|
||||||
# Ensure config.yaml was not overwritten (it may exist, but should remain original if we create it)
|
# Ensure config.yaml was not overwritten (it may exist, but should remain original if we create it)
|
||||||
|
|||||||
@@ -242,9 +242,7 @@ class LoadConfigIntegrationUnitTests(unittest.TestCase):
|
|||||||
# Provide package defaults via fake pkgmgr + pkg_root/config
|
# Provide package defaults via fake pkgmgr + pkg_root/config
|
||||||
root = Path(td) / "site-packages"
|
root = Path(td) / "site-packages"
|
||||||
pkg_root = root / "pkgmgr"
|
pkg_root = root / "pkgmgr"
|
||||||
cfg_dir = (
|
cfg_dir = pkg_root / "config"
|
||||||
root / "config"
|
|
||||||
) # NOTE: load.py checks multiple roots, including pkg_root.parent (=site-packages)
|
|
||||||
pkg_root.mkdir(parents=True)
|
pkg_root.mkdir(parents=True)
|
||||||
cfg_dir.mkdir(parents=True)
|
cfg_dir.mkdir(parents=True)
|
||||||
|
|
||||||
|
|||||||
Reference in new issue
Block a user