diff --git a/.ci/scripts/check_changelog_snippets.py b/.ci/scripts/check_changelog_snippets.py new file mode 100755 index 000000000..0fd9f8a53 --- /dev/null +++ b/.ci/scripts/check_changelog_snippets.py @@ -0,0 +1,36 @@ +#!/bin/env python3 +# /// script +# requires-python = ">=3.11" +# dependencies = [ +# ] +# /// +import re +import sys +from pathlib import Path + +import tomllib + +EXCEPTIONS = [ + ".TEMPLATE.md", + ".gitkeep", + "pulp-glue", +] + + +def snippet_valid(path: Path, changelog_exts: list[str]) -> bool: + return (path.name in EXCEPTIONS) or ( + path.suffix in changelog_exts + and (path.stem.startswith("+") or re.fullmatch(r"\d*", path.stem)) + ) + + +if __name__ == "__main__": + with Path("pyproject.toml").open("rb") as fp: + pyproject_toml = tomllib.load(fp) + changelog_exts = [ + f".{item['directory']}" for item in pyproject_toml["tool"]["towncrier"]["type"] + ] + matches = Path("CHANGES").rglob("*") + invalid_snippets = ", ".join(f"'{s}'" for s in matches if not snippet_valid(s, changelog_exts)) + if invalid_snippets: + sys.exit(f"LINT: Invalid changelog entries: {invalid_snippets}.") diff --git a/.ci/scripts/pr_labels.py b/.ci/scripts/pr_labels.py index b2350b6d7..214481563 100755 --- a/.ci/scripts/pr_labels.py +++ b/.ci/scripts/pr_labels.py @@ -16,7 +16,7 @@ from git import Repo -def main(): +def main() -> None: assert len(sys.argv) == 3 with Path("pyproject.toml").open("rb") as fp: diff --git a/.ci/scripts/validate_commit_message.py b/.ci/scripts/validate_commit_message.py index 7e678c178..3f30e318a 100644 --- a/.ci/scripts/validate_commit_message.py +++ b/.ci/scripts/validate_commit_message.py @@ -62,7 +62,9 @@ def check_changelog(issue: str) -> None: print(f"Checking commit message for {sha[0:7]}.") # validate the issue attached to the commit -issue_regex = r"(?:{keywords})[\s:]+#(\d+)".format(keywords=("|").join(KEYWORDS)) +issue_regex = r"(?:{keywords})[\s:]+#(\d+)".format( + keywords=("|").join(re.escape(k) for k in KEYWORDS) +) issues = re.findall(issue_regex, message, re.IGNORECASE) cherry_pick_regex = r"^\s*\(cherry picked from commit [0-9a-f]*\)\s*$" cherry_pick = re.search(cherry_pick_regex, message, re.MULTILINE) @@ -70,7 +72,7 @@ def check_changelog(issue: str) -> None: if issues: for issue in issues: if not cherry_pick: - check_status(issue) check_changelog(issue) + check_status(issue) print(f"Commit message for {sha[0:7]} passed.") diff --git a/Makefile b/Makefile index 328f4b3c8..68f61ad57 100644 --- a/Makefile +++ b/Makefile @@ -38,6 +38,7 @@ _lint: find tests .ci -name '*.sh' -print0 | xargs -0 shellcheck -x ruff format --check --diff ruff check --output-format concise + .ci/scripts/check_changelog_snippets.py .ci/scripts/check_click_for_mypy.py mypy cd pulp-glue; mypy diff --git a/cookiecutter/ci/{{ cookiecutter.__project_name }}/.ci/scripts/check_changelog_snippets.py b/cookiecutter/ci/{{ cookiecutter.__project_name }}/.ci/scripts/check_changelog_snippets.py new file mode 100755 index 000000000..11b4e871e --- /dev/null +++ b/cookiecutter/ci/{{ cookiecutter.__project_name }}/.ci/scripts/check_changelog_snippets.py @@ -0,0 +1,38 @@ +#!/bin/env python3 +# /// script +# requires-python = ">=3.11" +# dependencies = [ +# ] +# /// +import re +import sys +from pathlib import Path + +import tomllib + +EXCEPTIONS = [ + ".TEMPLATE.md", + ".gitkeep", + {%- if cookiecutter.glue %} + "pulp-glue{{ cookiecutter.__app_label_suffix}}", + {%- endif %} +] + + +def snippet_valid(path: Path, changelog_exts: list[str]) -> bool: + return (path.name in EXCEPTIONS) or ( + path.suffix in changelog_exts + and (path.stem.startswith("+") or re.fullmatch(r"\d*", path.stem)) + ) + + +if __name__ == "__main__": + with Path("pyproject.toml").open("rb") as fp: + pyproject_toml = tomllib.load(fp) + changelog_exts = [ + f".{item['directory']}" for item in pyproject_toml["tool"]["towncrier"]["type"] + ] + matches = Path("CHANGES").rglob("*") + invalid_snippets = ", ".join(f"'{s}'" for s in matches if not snippet_valid(s, changelog_exts)) + if invalid_snippets: + sys.exit(f"LINT: Invalid changelog entries: {invalid_snippets}.") diff --git a/cookiecutter/ci/{{ cookiecutter.__project_name }}/.ci/scripts/pr_labels.py b/cookiecutter/ci/{{ cookiecutter.__project_name }}/.ci/scripts/pr_labels.py index b2350b6d7..214481563 100755 --- a/cookiecutter/ci/{{ cookiecutter.__project_name }}/.ci/scripts/pr_labels.py +++ b/cookiecutter/ci/{{ cookiecutter.__project_name }}/.ci/scripts/pr_labels.py @@ -16,7 +16,7 @@ from git import Repo -def main(): +def main() -> None: assert len(sys.argv) == 3 with Path("pyproject.toml").open("rb") as fp: diff --git a/cookiecutter/ci/{{ cookiecutter.__project_name }}/.ci/scripts/validate_commit_message.py b/cookiecutter/ci/{{ cookiecutter.__project_name }}/.ci/scripts/validate_commit_message.py index df1a296cd..7d76a91e3 100644 --- a/cookiecutter/ci/{{ cookiecutter.__project_name }}/.ci/scripts/validate_commit_message.py +++ b/cookiecutter/ci/{{ cookiecutter.__project_name }}/.ci/scripts/validate_commit_message.py @@ -68,7 +68,9 @@ def check_changelog(issue: str) -> None: print(f"Checking commit message for {sha[0:7]}.") # validate the issue attached to the commit -issue_regex = r"(?:{keywords})[\s:]+#(\d+)".format(keywords=("|").join(KEYWORDS)) +issue_regex = r"(?:{keywords})[\s:]+#(\d+)".format( + keywords=("|").join(re.escape(k) for k in KEYWORDS) +) issues = re.findall(issue_regex, message, re.IGNORECASE) cherry_pick_regex = r"^\s*\(cherry picked from commit [0-9a-f]*\)\s*$" cherry_pick = re.search(cherry_pick_regex, message, re.MULTILINE) @@ -76,7 +78,7 @@ def check_changelog(issue: str) -> None: if issues: for issue in issues: if not cherry_pick: - check_status(issue) check_changelog(issue) + check_status(issue) print(f"Commit message for {sha[0:7]} passed.") diff --git a/cookiecutter/ci/{{ cookiecutter.__project_name }}/Makefile b/cookiecutter/ci/{{ cookiecutter.__project_name }}/Makefile index f55995571..2ee73289b 100644 --- a/cookiecutter/ci/{{ cookiecutter.__project_name }}/Makefile +++ b/cookiecutter/ci/{{ cookiecutter.__project_name }}/Makefile @@ -48,6 +48,7 @@ _lint: {%- if cookiecutter.glue and cookiecutter.app_label %} .ci/scripts/check_cli_dependencies.py {%- endif %} + .ci/scripts/check_changelog_snippets.py .ci/scripts/check_click_for_mypy.py mypy {%- if cookiecutter.glue %} diff --git a/cookiecutter/ci/{{ cookiecutter.__project_name }}/pyproject.toml.update b/cookiecutter/ci/{{ cookiecutter.__project_name }}/pyproject.toml.update index 56f13e557..f00297aad 100644 --- a/cookiecutter/ci/{{ cookiecutter.__project_name }}/pyproject.toml.update +++ b/cookiecutter/ci/{{ cookiecutter.__project_name }}/pyproject.toml.update @@ -11,7 +11,7 @@ dev = [ ] lint = [ {include-group = "test"}, - "mypy~=1.20.0", + "mypy~=2.4.0", "ruff~=0.16.0", "shellcheck-py~=0.11.0.1", "types-pygments",