Contributing¶
Contributions are welcome — bug reports, documentation fixes, and pull requests alike. This project follows the Django Commons Code of Conduct.
Before you open an issue or a pull request¶
This project is maintained in spare time. Response and review times depend on the available capacity and the scope of the work; a quick response to one submission does not set a timeline for others.
Keep submissions focused. Use one topic per issue and one change per pull request. Submit unrelated changes separately so each can be reviewed on its own.
Make reports actionable. Describe what you did, what you expected, and what happened instead. Include reproduction steps and relevant diagnostics so the problem can be investigated.
Review and understand your contribution. You are responsible for what you submit, including work produced with assistance. Check it before submitting and be prepared to explain each change.
Use a specific subject line. Name the affected command or feature and describe the problem or proposed change.
For questions about scope or direction, open a discussion or mail oliver@andrich.me before starting substantial work.
Reporting a bug¶
Run the diagnostics and paste their output — they cover most of what would otherwise be a round of questions:
python manage.py tailwind config # resolved paths and settings
python manage.py tailwind troubleshoot # common misconfigurations
python manage.py tailwind build --verbose
Add your OS, your Python and Django versions, the steps to reproduce, and the full traceback if there is one.
Development setup¶
The only prerequisite is mise; it provisions Python 3.10–3.15, uv, and
pre-commit from mise.toml.
git clone git@github.com:django-commons/django-tailwind-cli.git
cd django-tailwind-cli
mise install
mise run bootstrap
Tests and checks¶
mise run test # pytest with coverage on the default interpreter
mise run test-all # the full Python/Django matrix via tox
mise run lint # pre-commit: ruff, basedpyright, uv-secure, upgrade hooks
mise run test and mise run lint both have to pass before a pull request is ready. test-all
takes a while; CI runs the matrix on every pull request, so running it locally is optional unless
you are touching something version-specific.
One of the lint hooks, uv-secure, checks the lock file against published security advisories. If
it fails on a dependency your change never touched, a new advisory appeared and the lock needs a
bump — mention it in the pull request instead of working around it.
An optional dev session¶
mise run dev opens a three-pane tmux session — claude --continue on the left, pytest-watcher
running the suite on every save top right, a shell bottom right — on a tmux server of its own named
after the project, so stopping it can never disturb another project’s session.
mise run dev # start, or attach if it is already running
mise run dev stop # shut it down
mise run dev restart # from outside the session
mise run dev status
Entirely optional — nothing in the project depends on it. The watcher is also available on its own
as mise run test-watch; it runs without coverage so the feedback stays fast, and mise run test
remains the command that enforces it.
Commit messages¶
Conventional Commits in English, with a scope:
feat(management): add purge command for cleaning CSS
fix(config): handle prefixed staticfile directories
chore(deps): bump django-click to 2.5.0
Use the scope that names the area you touched. config, management, build, watch,
runserver, download, http, docs, tests, ci, and deps are the common ones; git log
shows the rest.
Keep the message short — a title plus one or two sentences on the why. The diff already shows what changed, and test counts or coverage numbers belong in the pull request, not in the history.
Commit under your own name and without bot co-author trailers. Whatever tools you used, the change is yours.
Changelog¶
User-facing changes get an entry in CHANGELOG.md under ## Unreleased, in one of the categories
the file already uses (Breaking Changes, New Features, Bug Fixes, Developer Experience, and so on).
One or two bullets, focused on what the change means for users rather than on how it was
implemented. Internal refactorings that nobody notices from the outside do not need one.
Releases¶
The major version tracks Tailwind CSS, not this package’s own compatibility. 4.x supports
Tailwind 4.x, and the major only moves when Tailwind’s does. A change that would be a major
elsewhere under semver — a dropped dependency, a different exit code — goes into a minor here and
is called out in the changelog instead.
Releasing is a tag. [tool.hatch.version] reads the version from git, so v4.8.0 is the
version. Pushing the tag starts the release rather than finishing it: the workflow builds, uploads
to TestPyPI, and then waits — the pypi environment requires a reviewer, so PyPI publishing needs
an admin to approve it in the GitHub UI. Do not push a tag and walk away.
Rename ## Unreleased to ## 4.8.0 (YYYY-MM-DD) before tagging. The release workflow extracts
that section for the GitHub release notes and fails if it cannot find one, which blocks the whole
release rather than shipping empty notes.
Pull requests¶
Fork the repository and work on a feature branch. Add tests for new behaviour and update the
documentation when the change is user-facing — docs/ is published to
Read the Docs, and the README is its landing page. New code
carries type hints; basedpyright runs in strict mode. CI runs the same pre-commit hooks that
mise run lint runs, so checking locally first saves you a red pipeline.
License¶
By contributing you agree that your contribution is licensed under the MIT license of this project.