Contributing¶

Contributions are welcome and don’t require writing Python.


We firmly believe that diverse backgrounds strengthen a product. Share your ideas regardless of your programming experience, half the time the most valuable contribution is naming a problem clearly.

Note

The MacAdmin community is full of people who quietly believe they’re the only one who doesn’t actually know what they’re doing. If you’ve ever second-guessed whether you should create an issue or reach out, or ever thought “it’s not worth it”, consider this an invitation to send the next one. Seriously. Reports of all types and all experiences are not only welcomed but genuinely encouraged.

How to Contribute¶

The shortest path is to get in touch first. Open a bug report, feature request, or feedback issue describing what you want to change before writing code. This avoids the “you already shipped the same thing in a branch” problem and lets us scope the change together.

Pull Request Workflow¶

We do our best to follow the standard GitHub flow:

Fork the Patcher repository.

Clone your fork locally.

$ git clone https://github.com/liquidz00/Patcher.git
$ gh repo clone liquidz00/Patcher /clone/destination/here

Branch off develop with a descriptive name.

$ git checkout develop
$ git checkout -b <fix|feat|perf|security>/your-description-here

Pull in latest changes before submitting a PR.

$ git fetch origin develop && git pull

Open the PR against the develop branch.

Important

PRs go to the develop branch, not main. The main branch tracks stable releases, ongoing development lands on develop and is merged to main as part of the release cut. Tests must pass on the pytest workflow before a PR can merge.

Environment Setup¶

Patcher uses uv for dependency management and ruff for formatting and linting. The Makefile wraps the common workflows.

Execute after cloning your fork¶
$ make dev

make dev creates a virtual environment if one doesn’t exist, installs both the patcherctl and patcher-api workspace members with all optional extras (dev, docs, test), and is the canonical setup target for contributors. Re-run it after pulling changes that touch any pyproject.toml.

Note

The Xcode Command Line Tools are required for the make command on macOS.

Vendor Documentation Submodules¶

Patcher pins read-only copies of the Installomator and AutoPkg wikis as git submodules under vendor-docs/. These exist as a reference for contributors and for Claude when reasoning about upstream behavior and are not surfaced into the user-facing docs.

Initialize vendor docs¶
$ make init-vendor-docs
Refresh against latest upstream when needed¶
$ make update-vendor-docs

These are completely optional, working without them is totally fine. Every Patcher build, test, and doc target runs without the submodules populated.

Makefile Commands¶

The complete list of available targets, sourced from make help¶
help                   Show this help message
venv                   Create virtual environment if missing
install                Install base dependencies (Patcher only)
dev                    Install everything for monorepo development (Patcher + API + all extras)
uninstall              Remove the .venv directory
clean                  Remove caches, build artifacts, and the .venv
lint                   Check code style with ruff
format                 Auto-format code with ruff
lock                   Update uv.lock
upgrade                Upgrade all dependencies to latest versions
test                   Run Patcher unit tests (excludes integration)
test-integration       Run Patcher integration tests only
smoke-test             Hand-run smoke check of PatcherClient against a live Jamf instance
test-api               Run Patcher API tests
serve-api              Run Patcher API locally with hot-reload
pre-commit             Install pre-commit hooks
pre-commit-run         Run pre-commit on all files
pre-commit-update      Update pre-commit hooks to latest versions
build                  Build distribution packages (sdist + wheel)
docs                   Build Sphinx documentation
init-vendor-docs       One-time after clone - pull submodule content
update-vendor-docs     Refresh vendor docs to latest upstream (Installomator/Autopkg Wikis)

Tip

make lint before opening a PR catches the same checks CI runs. make format auto-fixes the mechanical ones. Neither is strictly required (pre-commit hooks and the GitHub runner enforce both), but running them locally is faster than waiting for CI to fail.

Testing¶

Three suites, each shaped for a different feedback loop. Together they cover what changes between commits, what changes when Jamf changes, and what changes when the catalog ingest changes.

        %%{init: {'theme':'base', 'themeVariables': {'fontSize': '14px'}}}%%
flowchart LR
  Dev([Developer])
  Unit[Unit<br/>tests/]
  API[API<br/>api/tests/]
  Integration[Integration<br/>tests/integration/]
  Live[(dummy.jamfcloud.com)]
  CI{{Every PR}}
  Merge([Merge])

  Dev --> Unit
  Dev --> API
  Dev -. opt-in .-> Integration
  Integration -. real HTTP .-> Live
  Unit --> CI
  API --> CI
  CI --> Merge
    

Why Three Suites¶

Unit tests (tests/, excluding tests/integration/) mock the HTTP and filesystem boundaries. They run on every PR and finish fast because the boundary is the contract that matters: when Jamf’s API contract is stable, the unit suite is enough to catch regressions in our parsing, matching, and report-building code. This is the suite that turns red when you break refactor work.

API tests (api/tests/) cover the catalog service itself. Ingest from upstream sources, stitch logic, FastAPI routes, and ETag behavior. They’re separate from the package tests because the API can ship independently, but they run alongside the unit suite on every PR.

Integration tests (tests/integration/) hit Jamf’s public dummy instance with no mocks at the HTTP boundary. They validate the full chain — credential loading, token flow, real HTTP, response parsing — against actual Jamf Pro responses. They are not in CI because hitting a shared dummy on every PR is just plain rude. Run make test-integration locally before pushing anything that touches request shaping or response parsing.

The pytest workflow badge on the landing page is the live signal for unit and API tests. Click it for the latest run.

Note

Treat integration tests to the dummy instance as smoke-test coverage, not exhaustive validation. Configure environment variables if you would like to leverage your own Jamf pro tenant for integration tests.

Pointing integration tests at your own test tenant¶
$ export PATCHER_INTEGRATION_URL="https://your-tenant.jamfcloud.com"
$ export PATCHER_INTEGRATION_CLIENT_ID="..."
$ export PATCHER_INTEGRATION_CLIENT_SECRET="..."
$ make test-integration

Next Steps¶

If anything in this guide is unclear, reach out on the #patcher channel in MacAdmins Slack. The maintainers are active there.

If you’re new to the GitHub PR flow, GitHub’s own documentation is the canonical walkthrough. The Keep on Coding YouTube video is an option for visual learners.