Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 27 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,40 @@ on:
- ".github/workflows/release.yml"

jobs:
test:
name: Run tests
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
python-version: ["3.10", "3.11", "3.12", "3.13", "3.14"]

steps:
- uses: actions/checkout@v4

- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}

- name: Install package
run: |
python -m pip install --upgrade pip
pip install -e .

- name: Run tests
run: python -m unittest discover -s tests -v

release:
name: Create GitHub Release
needs: test
runs-on: ubuntu-latest

steps:
- name: Checkout source code
uses: actions/checkout@v4
with:
fetch-depth: 0

- name: Set up Python
uses: actions/setup-python@v5
Expand Down
20 changes: 19 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,23 @@ All notable changes to this project will be documented in this file. This projec

Nothing yet.

## [1.25.1] - 2026-07-15

### Added

- `check-munki-pkgsinfo` and `check-autopkg-recipes` now validate the type of `allow_untrusted`, `icon_hash`, `installed_size_staged`, `uninstaller_item_hash`, and `uninstaller_item_size` pkginfo keys. These are set by current Munki 7 code but aren't yet documented on the Munki wiki.

### Changed

- Clarified RELEASING.md on which version to use when preparing a release, since the release workflow auto-bumps `setup.py` on `dev` to the next patch version after each release.

### Fixed

- Fixed a bug in `generate_autopkg_processor_versions.py` that caused argument-introduction versions to be miscalculated for any AutoPkg release with Python 2-only syntax. The regex/tokenizer fallback parser used for those releases only recognized the first key of each `input_variables` dict, so most arguments were misattributed to whichever later release first became parseable by Python 3's `ast` module (typically reported as AutoPkg 1.1), causing `check-autopkg-recipes` to falsely flag long-standing recipes.
- The release workflow now runs the test suite and requires it to pass before publishing a release.
- `check-autopkg-recipes` now reports an invalid `MinimumVersion` string as a lint error instead of crashing with an uncaught exception.
- `validate_supported_architectures` no longer crashes with a `TypeError` when `supported_architectures` is present but not a list; the type mismatch is still reported by `validate_pkginfo_key_types`.

## [1.25.0] - 2026-06-21

### Added
Expand Down Expand Up @@ -504,7 +521,8 @@ Nothing yet.

- Initial release

[Unreleased]: https://github.com/homebysix/pre-commit-macadmin/compare/v1.25.0...HEAD
[Unreleased]: https://github.com/homebysix/pre-commit-macadmin/compare/v1.25.1...HEAD
[1.25.1]: https://github.com/homebysix/pre-commit-macadmin/compare/v1.25.0...v1.25.1
[1.25.0]: https://github.com/homebysix/pre-commit-macadmin/compare/v1.24.1...v1.25.0
[1.24.1]: https://github.com/homebysix/pre-commit-macadmin/compare/v1.24.0...v1.24.1
[1.24.0]: https://github.com/homebysix/pre-commit-macadmin/compare/v1.23.0...v1.24.0
Expand Down
8 changes: 4 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ For any hook in this repo you wish to use, add the following to your pre-commit

```yaml
- repo: https://github.com/homebysix/pre-commit-macadmin
rev: v1.25.0
rev: v1.25.1
hooks:
- id: check-plists
# - id: ...
Expand Down Expand Up @@ -148,7 +148,7 @@ When combining arguments that take lists (for example: `--required-keys`, `--cat

```yaml
- repo: https://github.com/homebysix/pre-commit-macadmin
rev: v1.25.0
rev: v1.25.1
hooks:
- id: check-munki-pkgsinfo
args: ['--catalogs', 'testing', 'stable', '--']
Expand All @@ -158,7 +158,7 @@ But if you also use the `--categories` argument, you would move the trailing `--

```yaml
- repo: https://github.com/homebysix/pre-commit-macadmin
rev: v1.25.0
rev: v1.25.1
hooks:
- id: check-munki-pkgsinfo
args: ['--catalogs', 'testing', 'stable', '--categories', 'Design', 'Engineering', 'Web Browsers', '--']
Expand All @@ -170,7 +170,7 @@ If it looks better to your eye, feel free to use a multi-line list for long argu

```yaml
- repo: https://github.com/homebysix/pre-commit-macadmin
rev: v1.25.0
rev: v1.25.1
hooks:
- id: check-munki-pkgsinfo
args: [
Expand Down
6 changes: 4 additions & 2 deletions RELEASING.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ Releases are largely automated via GitHub Actions. The workflow triggers when `s

## Release Process

1. Determine the version to release. After each release, the workflow auto-bumps `setup.py` on `dev` to the next **patch** version — so for a normal patch release, `setup.py` on `dev` already holds the correct next version; use that value as-is in the steps below. Only edit `setup.py` yourself first if you're bumping the **minor** or **major** version instead (see [Version Numbering](#version-numbering)).

1. On the `dev` branch, check unit tests:

.venv/bin/python -m coverage run -m unittest discover -vs tests
Expand All @@ -15,7 +17,7 @@ Releases are largely automated via GitHub Actions. The workflow triggers when `s

Use `--full` when the generator logic changes, historical AutoPkg tags are added or corrected, or you need to rebuild from AutoPkg `0.1.0`. The default mode appends stable releases newer than the checked-in `last_walked_version`.

1. Prepare CHANGELOG.md for release by moving `[Unreleased]` changes to a new version section:
1. Prepare CHANGELOG.md for release by moving `[Unreleased]` changes to a new version section, using the version determined in step 1 above for `X.Y.Z`:

## [Unreleased]

Expand All @@ -32,7 +34,7 @@ Releases are largely automated via GitHub Actions. The workflow triggers when `s

[X.Y.Z]: https://github.com/homebysix/pre-commit-macadmin/compare/vPREVIOUS...vX.Y.Z

1. Update the version in `setup.py` to match the CHANGELOG version (e.g., `2.3.6`).
1. Confirm the version in `setup.py` matches the CHANGELOG version (e.g., `2.3.6`). For a normal patch release this should already be true from the auto-bump; only change it here if you're doing a minor/major release.

1. Update the version in `README.md` examples to match (e.g., `rev: v2.3.6`).

Expand Down
36 changes: 7 additions & 29 deletions pre_commit_macadmin_hooks/autopkg_processor_versions.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,6 @@
},
"AppPkgCreator": {
"_introduced_": "1.0",
"bundleid": "1.1",
"force_pkg_build": "1.1",
"pkg_path": "1.1",
"version": "1.1",
"version_key": "2.4.1",
},
"BrewCaskInfoProvider": {
Expand Down Expand Up @@ -40,8 +36,6 @@
},
"Copier": {
"_introduced_": "0.1.0",
"destination_path": "1.1",
"overwrite": "1.1",
},
"DeprecationWarning": {
"_introduced_": "1.1",
Expand All @@ -61,7 +55,7 @@
},
"FileCreator": {
"_introduced_": "0.1.0",
"file_mode": "1.1",
"file_mode": "0.5.0",
},
"FileFinder": {
"_introduced_": "0.2.3",
Expand All @@ -74,7 +68,6 @@
},
"FlatPkgPacker": {
"_introduced_": "0.2.4",
"destination_pkg": "1.1",
},
"FlatPkgUnpacker": {
"_introduced_": "0.1.0",
Expand All @@ -101,20 +94,14 @@
"MunkiImporter": {
"_introduced_": "0.1.0",
"MUNKILIB_DIR": "2.2",
"MUNKI_PKGINFO_FILE_EXTENSION": "1.1",
"MUNKI_PKGINFO_FILE_EXTENSION": "0.3.0",
"MUNKI_REPO": "0.2.5",
"MUNKI_REPO_PLUGIN": "2.2",
"additional_makepkginfo_options": "1.1",
"extract_icon": "2.2",
"force_munki_repo_lib": "2.2",
"force_munkiimport": "1.1",
"metadata_additions": "1.1",
"munkiimport_appname": "1.1",
"munkiimport_pkgname": "1.1",
"pkginfo": "1.1",
"repo_subdirectory": "1.1",
"uninstaller_pkg_path": "1.1",
"version_comparison_key": "1.1",
"uninstaller_pkg_path": "1.0.0",
"version_comparison_key": "0.3.0",
},
"MunkiInfoCreator": {
"_introduced_": "0.1.0",
Expand Down Expand Up @@ -148,7 +135,6 @@
},
"PkgExtractor": {
"_introduced_": "0.1.0",
"extract_root": "1.1",
},
"PkgInfoCreator": {
"_introduced_": "0.1.0",
Expand All @@ -161,12 +147,9 @@
},
"PlistEditor": {
"_introduced_": "0.1.0",
"output_plist_path": "1.1",
"plist_data": "1.1",
},
"PlistReader": {
"_introduced_": "0.2.5",
"plist_keys": "0.4.0",
},
"SignToolVerifier": {
"_introduced_": "2.3",
Expand All @@ -187,14 +170,10 @@
},
"URLDownloader": {
"_introduced_": "0.1.0",
"CHECK_FILESIZE_ONLY": "1.1",
"CURL_PATH": "1.1",
"PKG": "1.1",
"curl_opts": "1.1",
"download_dir": "1.1",
"filename": "1.1",
"CHECK_FILESIZE_ONLY": "0.6.0",
"CURL_PATH": "0.6.0",
"curl_opts": "1.0.4",
"prefetch_filename": "1.4",
"request_headers": "1.1",
},
"URLDownloaderPython": {
"_introduced_": "2.4.1",
Expand All @@ -217,7 +196,6 @@
},
"Versioner": {
"_introduced_": "0.1.0",
"plist_version_key": "1.1",
"skip_single_root_dir": "2.3",
},
}
Expand Down
13 changes: 10 additions & 3 deletions pre_commit_macadmin_hooks/check_autopkg_recipes.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
import sys
from typing import Any

from packaging.version import Version
from packaging.version import InvalidVersion, Version

from pre_commit_macadmin_hooks.autopkg_processor_versions import PROC_VERSIONS
from pre_commit_macadmin_hooks.util import (
Expand Down Expand Up @@ -190,7 +190,14 @@ def validate_minimumversion(process, min_vers, ignore_min_vers_before, filename)
# Validate that the MinimumVersion value is a string
if not isinstance(min_vers, str):
print(f"{filename}: MinimumVersion should be a string.")
passed = False
return False

# Validate that the MinimumVersion value is a parseable version string.
try:
min_vers_version = Version(min_vers)
except InvalidVersion:
print(f"{filename}: MinimumVersion {min_vers!r} is not a valid version string.")
return False

# Validate that the MinimumVersion value fits the processors and processor
# arguments used. Unknown processors and unknown arguments fail open.
Expand All @@ -213,7 +220,7 @@ def validate_minimumversion(process, min_vers, ignore_min_vers_before, filename)
if Version(required_version) < Version(ignore_min_vers_before):
continue

if Version(str(min_vers)) < Version(required_version):
if min_vers_version < Version(required_version):
print(
f"{filename}: {proc_name} processor requires minimum AutoPkg "
f"version {required_version}"
Expand Down
24 changes: 19 additions & 5 deletions pre_commit_macadmin_hooks/util.py
Original file line number Diff line number Diff line change
Expand Up @@ -192,9 +192,15 @@ def validate_supported_architectures(
"""
passed = True
allowed_values = ("arm64", "x86_64")
if "supported_architectures" in pkginfo:
for arch in pkginfo["supported_architectures"]:
if recipe_mode and arch.startswith("%") and arch.endswith("%"):
architectures = pkginfo.get("supported_architectures")
if isinstance(architectures, list):
for arch in architectures:
if (
recipe_mode
and isinstance(arch, str)
and arch.startswith("%")
and arch.endswith("%")
):
# Skip values that are substituted during AutoPkg recipe runs
continue
if arch not in allowed_values:
Expand All @@ -212,10 +218,14 @@ def validate_pkginfo_key_types(pkginfo: dict[str, Any], filename: str) -> bool:
"""

# Pkginfo keys and their known types. Omitted keys are left unvalidated.
# Source: https://github.com/munki/munki/wiki/Supported-Pkginfo-Keys
# Last updated 2019-03-13.
# Source: https://github.com/munki/munki/wiki/Supported-Pkginfo-Keys, plus
# keys confirmed against the Munki 7 Swift source that aren't yet
# documented on the wiki (allow_untrusted, icon_hash, installed_size_staged,
# uninstaller_item_hash, uninstaller_item_size).
# Last verified complete against the wiki 2026-06-30 and Munki source 2026-07-15.
pkginfo_types = {
"additional_startosinstall_options": list,
"allow_untrusted": bool,
"apple_item": bool,
"autoremove": bool,
"blocking_applications": list,
Expand All @@ -232,10 +242,12 @@ def validate_pkginfo_key_types(pkginfo: dict[str, Any], filename: str) -> bool:
"force_install_after_date": datetime,
"forced_install": bool,
"forced_uninstall": bool,
"icon_hash": str,
"icon_name": str,
"installable_condition": str,
"installcheck_script": str,
"installed_size": int,
"installed_size_staged": int,
"installer_choices_xml": list,
"installer_environment": dict,
"installer_item_hash": str,
Expand Down Expand Up @@ -273,7 +285,9 @@ def validate_pkginfo_key_types(pkginfo: dict[str, Any], filename: str) -> bool:
"uninstall_script": str,
"uninstallable": bool,
"uninstallcheck_script": str,
"uninstaller_item_hash": str,
"uninstaller_item_location": str,
"uninstaller_item_size": int,
"update_for": list,
"version": str,
"version_script": str,
Expand Down
3 changes: 1 addition & 2 deletions scripts/generate_autopkg_processor_versions.py
Original file line number Diff line number Diff line change
Expand Up @@ -286,8 +286,7 @@ def string_keys_at_top_dict_level(dict_source: str) -> set[str]:
level += 1
elif token.string in "}])":
level -= 1
if token.string not in ",\n":
previous_significant = token.string
previous_significant = token.string
continue
if token.type == tokenize.STRING and level == 1:
try:
Expand Down
3 changes: 2 additions & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,11 @@
name="pre-commit-macadmin",
description="Pre-commit hooks for Mac admins, client engineers, and IT consultants.",
url="https://github.com/homebysix/pre-commit-macadmin",
version="1.25.0",
version="1.25.1",
author="Elliot Jordan",
author_email="elliot@elliotjordan.com",
packages=["pre_commit_macadmin_hooks"],
python_requires=">=3.10",
install_requires=["ruamel.yaml>=0.15", "packaging>=23.2"],
entry_points={
"console_scripts": [
Expand Down
11 changes: 11 additions & 0 deletions tests/test_check_autopkg_recipes.py
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,17 @@ def test_validate_minimumversion_non_string(self):
self.assertFalse(result)
mock_print.assert_called_with("file.recipe: MinimumVersion should be a string.")

def test_validate_minimumversion_invalid_string(self):
process = [{"Processor": "AppPkgCreator"}]
with mock.patch("builtins.print") as mock_print:
result = target.validate_minimumversion(
process, "not-a-version", "1.0", "file.recipe"
)
self.assertFalse(result)
mock_print.assert_called_with(
"file.recipe: MinimumVersion 'not-a-version' is not a valid version string."
)

def test_validate_minimumversion_too_low(self):
process = [{"Processor": "AppPkgCreator"}]
with mock.patch("builtins.print") as mock_print:
Expand Down
Loading
Loading