django-tailwind-cli 4.6.2__tar.gz → 4.8.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {django_tailwind_cli-4.6.2 → django_tailwind_cli-4.8.0}/.github/workflows/release.yml +8 -5
- django_tailwind_cli-4.8.0/.github/workflows/test.yml +193 -0
- {django_tailwind_cli-4.6.2 → django_tailwind_cli-4.8.0}/.gitignore +5 -3
- django_tailwind_cli-4.8.0/.mise/tasks/dev +168 -0
- {django_tailwind_cli-4.6.2 → django_tailwind_cli-4.8.0}/CHANGELOG.md +72 -1
- django_tailwind_cli-4.8.0/CONTRIBUTING.md +142 -0
- django_tailwind_cli-4.8.0/PKG-INFO +267 -0
- django_tailwind_cli-4.8.0/README.md +230 -0
- django_tailwind_cli-4.8.0/docs/base_template.md +8 -0
- {django_tailwind_cli-4.6.2 → django_tailwind_cli-4.8.0}/docs/conf.py +10 -0
- django_tailwind_cli-4.8.0/docs/contributing.md +2 -0
- {django_tailwind_cli-4.6.2 → django_tailwind_cli-4.8.0}/docs/index.md +3 -1
- {django_tailwind_cli-4.6.2 → django_tailwind_cli-4.8.0}/docs/settings.md +19 -13
- {django_tailwind_cli-4.6.2 → django_tailwind_cli-4.8.0}/docs/template_tags.md +3 -3
- {django_tailwind_cli-4.6.2 → django_tailwind_cli-4.8.0}/docs/usage.md +11 -7
- django_tailwind_cli-4.8.0/docs/whitenoise.md +97 -0
- django_tailwind_cli-4.6.2/docs/development.md → django_tailwind_cli-4.8.0/docs/workflow.md +8 -28
- django_tailwind_cli-4.8.0/mise.toml +69 -0
- {django_tailwind_cli-4.6.2 → django_tailwind_cli-4.8.0}/pyproject.toml +29 -7
- {django_tailwind_cli-4.6.2 → django_tailwind_cli-4.8.0}/src/django_tailwind_cli/apps.py +6 -0
- django_tailwind_cli-4.8.0/src/django_tailwind_cli/checks.py +50 -0
- {django_tailwind_cli-4.6.2 → django_tailwind_cli-4.8.0}/src/django_tailwind_cli/config.py +158 -51
- django_tailwind_cli-4.8.0/src/django_tailwind_cli/management/commands/_build.py +163 -0
- django_tailwind_cli-4.8.0/src/django_tailwind_cli/management/commands/_download.py +163 -0
- django_tailwind_cli-4.8.0/src/django_tailwind_cli/management/commands/_errors.py +148 -0
- django_tailwind_cli-4.8.0/src/django_tailwind_cli/management/commands/_group.py +283 -0
- django_tailwind_cli-4.8.0/src/django_tailwind_cli/management/commands/_guides.py +294 -0
- django_tailwind_cli-4.8.0/src/django_tailwind_cli/management/commands/_process.py +251 -0
- django_tailwind_cli-4.8.0/src/django_tailwind_cli/management/commands/_source_css.py +256 -0
- django_tailwind_cli-4.8.0/src/django_tailwind_cli/management/commands/tailwind.py +622 -0
- {django_tailwind_cli-4.6.2 → django_tailwind_cli-4.8.0}/src/django_tailwind_cli/templatetags/tailwind_cli.py +4 -6
- {django_tailwind_cli-4.6.2 → django_tailwind_cli-4.8.0}/src/django_tailwind_cli/utils/http.py +9 -3
- django_tailwind_cli-4.8.0/tests/conftest.py +200 -0
- django_tailwind_cli-4.8.0/tests/helpers.py +46 -0
- django_tailwind_cli-4.8.0/tests/test_additional_commands.py +488 -0
- django_tailwind_cli-4.8.0/tests/test_checks.py +209 -0
- {django_tailwind_cli-4.6.2 → django_tailwind_cli-4.8.0}/tests/test_config.py +132 -127
- {django_tailwind_cli-4.6.2 → django_tailwind_cli-4.8.0}/tests/test_error_scenarios.py +371 -181
- {django_tailwind_cli-4.6.2 → django_tailwind_cli-4.8.0}/tests/test_http.py +69 -28
- {django_tailwind_cli-4.6.2 → django_tailwind_cli-4.8.0}/tests/test_integration.py +84 -335
- {django_tailwind_cli-4.6.2 → django_tailwind_cli-4.8.0}/tests/test_management_commands.py +446 -122
- {django_tailwind_cli-4.6.2 → django_tailwind_cli-4.8.0}/tox.ini +2 -0
- django_tailwind_cli-4.8.0/uv.lock +729 -0
- django_tailwind_cli-4.6.2/.github/workflows/test.yml +0 -53
- django_tailwind_cli-4.6.2/PKG-INFO +0 -376
- django_tailwind_cli-4.6.2/README.md +0 -323
- django_tailwind_cli-4.6.2/docs/base_template.md +0 -23
- django_tailwind_cli-4.6.2/justfile +0 -61
- django_tailwind_cli-4.6.2/src/django_tailwind_cli/management/commands/tailwind.py +0 -1717
- django_tailwind_cli-4.6.2/tests/conftest.py +0 -0
- django_tailwind_cli-4.6.2/tests/test_additional_commands.py +0 -311
- django_tailwind_cli-4.6.2/uv.lock +0 -690
- {django_tailwind_cli-4.6.2 → django_tailwind_cli-4.8.0}/.github/dependabot.yml +0 -0
- {django_tailwind_cli-4.6.2 → django_tailwind_cli-4.8.0}/.pre-commit-config.yaml +0 -0
- {django_tailwind_cli-4.6.2 → django_tailwind_cli-4.8.0}/.readthedocs.yml +0 -0
- {django_tailwind_cli-4.6.2 → django_tailwind_cli-4.8.0}/CODE_OF_CONDUCT.md +0 -0
- {django_tailwind_cli-4.6.2 → django_tailwind_cli-4.8.0}/LICENSE +0 -0
- {django_tailwind_cli-4.6.2 → django_tailwind_cli-4.8.0}/docs/changelog.md +0 -0
- {django_tailwind_cli-4.6.2 → django_tailwind_cli-4.8.0}/docs/installation.md +0 -0
- {django_tailwind_cli-4.6.2 → django_tailwind_cli-4.8.0}/docs/requirements.txt +0 -0
- {django_tailwind_cli-4.6.2 → django_tailwind_cli-4.8.0}/src/django_tailwind_cli/__init__.py +0 -0
- {django_tailwind_cli-4.6.2 → django_tailwind_cli-4.8.0}/src/django_tailwind_cli/management/__init__.py +0 -0
- {django_tailwind_cli-4.6.2 → django_tailwind_cli-4.8.0}/src/django_tailwind_cli/management/commands/__init__.py +0 -0
- {django_tailwind_cli-4.6.2 → django_tailwind_cli-4.8.0}/src/django_tailwind_cli/py.typed +0 -0
- {django_tailwind_cli-4.6.2 → django_tailwind_cli-4.8.0}/src/django_tailwind_cli/templates/tailwind_cli/base.html +0 -0
- {django_tailwind_cli-4.6.2 → django_tailwind_cli-4.8.0}/src/django_tailwind_cli/templates/tailwind_cli/tailwind_css.html +0 -0
- {django_tailwind_cli-4.6.2 → django_tailwind_cli-4.8.0}/src/django_tailwind_cli/templatetags/__init__.py +0 -0
- {django_tailwind_cli-4.6.2 → django_tailwind_cli-4.8.0}/src/django_tailwind_cli/utils/__init__.py +0 -0
- {django_tailwind_cli-4.6.2 → django_tailwind_cli-4.8.0}/tests/.gitignore +0 -0
- {django_tailwind_cli-4.6.2 → django_tailwind_cli-4.8.0}/tests/__init__.py +0 -0
- {django_tailwind_cli-4.6.2 → django_tailwind_cli-4.8.0}/tests/assets/css/.gitkeep +0 -0
- {django_tailwind_cli-4.6.2 → django_tailwind_cli-4.8.0}/tests/assets/css/tailwind.css +0 -0
- {django_tailwind_cli-4.6.2 → django_tailwind_cli-4.8.0}/tests/settings.py +0 -0
- {django_tailwind_cli-4.6.2 → django_tailwind_cli-4.8.0}/tests/templates/tests/base.html +0 -0
- {django_tailwind_cli-4.6.2 → django_tailwind_cli-4.8.0}/tests/templates/tests/dummy.email +0 -0
- {django_tailwind_cli-4.6.2 → django_tailwind_cli-4.8.0}/tests/test_tailwind_css_tag.py +0 -0
|
@@ -21,11 +21,11 @@ jobs:
|
|
|
21
21
|
runs-on: ubuntu-latest
|
|
22
22
|
|
|
23
23
|
steps:
|
|
24
|
-
- uses: actions/checkout@
|
|
24
|
+
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
25
25
|
with:
|
|
26
26
|
persist-credentials: false
|
|
27
27
|
- name: Set up Python
|
|
28
|
-
uses: actions/setup-python@
|
|
28
|
+
uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
|
|
29
29
|
with:
|
|
30
30
|
python-version: "3.x"
|
|
31
31
|
- name: Install pypa/build
|
|
@@ -67,8 +67,11 @@ jobs:
|
|
|
67
67
|
name: >-
|
|
68
68
|
Publish Python 🐍 distribution 📦 to PyPI
|
|
69
69
|
if: startsWith(github.ref, 'refs/tags/') # only publish to PyPI on tag pushes
|
|
70
|
+
# TestPyPI first: the upload to PyPI cannot be undone, so a broken artifact
|
|
71
|
+
# has to fail somewhere reversible before it gets there.
|
|
70
72
|
needs:
|
|
71
73
|
- build
|
|
74
|
+
- publish-to-testpypi
|
|
72
75
|
runs-on: ubuntu-latest
|
|
73
76
|
environment:
|
|
74
77
|
name: pypi
|
|
@@ -82,7 +85,7 @@ jobs:
|
|
|
82
85
|
name: python-package-distributions
|
|
83
86
|
path: dist/
|
|
84
87
|
- name: Publish distribution 📦 to PyPI
|
|
85
|
-
uses: pypa/gh-action-pypi-publish@
|
|
88
|
+
uses: pypa/gh-action-pypi-publish@dc37677b2e1c63e2034f94d8a5b11f265b73ba33 # release/v1.14
|
|
86
89
|
|
|
87
90
|
github-release:
|
|
88
91
|
name: >-
|
|
@@ -107,7 +110,7 @@ jobs:
|
|
|
107
110
|
with:
|
|
108
111
|
name: release-notes
|
|
109
112
|
- name: Sign the dists with Sigstore
|
|
110
|
-
uses: sigstore/gh-action-sigstore-python@
|
|
113
|
+
uses: sigstore/gh-action-sigstore-python@790bc6befb9d733738f18d8f895854b453640ec9 # v3.5.0
|
|
111
114
|
with:
|
|
112
115
|
inputs: >-
|
|
113
116
|
./dist/*.tar.gz
|
|
@@ -156,7 +159,7 @@ jobs:
|
|
|
156
159
|
name: python-package-distributions
|
|
157
160
|
path: dist/
|
|
158
161
|
- name: Publish distribution 📦 to TestPyPI
|
|
159
|
-
uses: pypa/gh-action-pypi-publish@
|
|
162
|
+
uses: pypa/gh-action-pypi-publish@dc37677b2e1c63e2034f94d8a5b11f265b73ba33 # release/v1.14
|
|
160
163
|
with:
|
|
161
164
|
repository-url: https://test.pypi.org/legacy/
|
|
162
165
|
skip-existing: true
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
name: Test
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches:
|
|
6
|
+
- main
|
|
7
|
+
pull_request:
|
|
8
|
+
|
|
9
|
+
concurrency:
|
|
10
|
+
group: ${{ github.workflow }}-${{ github.ref }}
|
|
11
|
+
cancel-in-progress: true
|
|
12
|
+
|
|
13
|
+
permissions:
|
|
14
|
+
contents: read
|
|
15
|
+
|
|
16
|
+
jobs:
|
|
17
|
+
lint:
|
|
18
|
+
name: Lint
|
|
19
|
+
runs-on: ubuntu-latest
|
|
20
|
+
|
|
21
|
+
# pre-commit pins its hook environments to 3.10 (default_language_version),
|
|
22
|
+
# and the linters only ever need one interpreter.
|
|
23
|
+
env:
|
|
24
|
+
MISE_PYTHON_VERSION: "3.10"
|
|
25
|
+
# tmux only exists for the optional `mise run dev` session, which CI never starts. Disabling
|
|
26
|
+
# beats limiting install_args, because `mise run` would auto-install it again.
|
|
27
|
+
MISE_DISABLE_TOOLS: tmux
|
|
28
|
+
|
|
29
|
+
steps:
|
|
30
|
+
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
31
|
+
with:
|
|
32
|
+
persist-credentials: false
|
|
33
|
+
|
|
34
|
+
- name: Set up mise
|
|
35
|
+
uses: jdx/mise-action@3c2e0cf82a5b2e5249f0d3635a4d83d0ae861518 # v4.2.5
|
|
36
|
+
with:
|
|
37
|
+
install: true
|
|
38
|
+
cache: true
|
|
39
|
+
# Distinct from the test jobs: this one also installs pre-commit, so
|
|
40
|
+
# sharing their key would hand them a cache missing a tool. The
|
|
41
|
+
# version belongs in the key too, or bumping it later restores a
|
|
42
|
+
# cache holding the old interpreter and never re-saves.
|
|
43
|
+
cache_key: "lint-{{default}}-{{env.MISE_PYTHON_VERSION}}"
|
|
44
|
+
|
|
45
|
+
- name: Cache pre-commit hooks
|
|
46
|
+
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
|
|
47
|
+
with:
|
|
48
|
+
path: ~/.cache/pre-commit
|
|
49
|
+
key: pre-commit-${{ runner.os }}-${{ hashFiles('.pre-commit-config.yaml') }}
|
|
50
|
+
restore-keys: pre-commit-${{ runner.os }}-
|
|
51
|
+
|
|
52
|
+
- name: Cache uv downloads
|
|
53
|
+
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
|
|
54
|
+
with:
|
|
55
|
+
path: ~/.cache/uv
|
|
56
|
+
key: uv-${{ runner.os }}-lint-${{ hashFiles('uv.lock') }}
|
|
57
|
+
restore-keys: uv-${{ runner.os }}-lint-
|
|
58
|
+
|
|
59
|
+
# basedpyright resolves imports through .venv (see [tool.pyright] in
|
|
60
|
+
# pyproject.toml). Without this step it reports every import as missing,
|
|
61
|
+
# which is why the original pre-commit job was dropped back in 2022.
|
|
62
|
+
# --locked, unlike `mise run bootstrap`: a uv.lock that has drifted from
|
|
63
|
+
# pyproject.toml should fail the job, not be silently re-resolved into
|
|
64
|
+
# something uv-secure then audits instead of the committed lock.
|
|
65
|
+
- name: Install dependencies
|
|
66
|
+
run: uv sync --all-extras --locked
|
|
67
|
+
|
|
68
|
+
# uv-secure audits uv.lock against the published advisories, so a new one
|
|
69
|
+
# can turn an unrelated pull request red. That is deliberate: it is how we
|
|
70
|
+
# hear about it. Fix the lock rather than skipping the hook.
|
|
71
|
+
- name: Run pre-commit
|
|
72
|
+
run: mise run lint
|
|
73
|
+
|
|
74
|
+
build:
|
|
75
|
+
name: Test (Python ${{ matrix.python-version }})
|
|
76
|
+
runs-on: ubuntu-latest
|
|
77
|
+
strategy:
|
|
78
|
+
matrix:
|
|
79
|
+
python-version: ["3.10", "3.11", "3.12", "3.13", "3.14", "3.15"]
|
|
80
|
+
|
|
81
|
+
# Job-scoped so every step resolves the same interpreter, not just the
|
|
82
|
+
# install step. Prereleases are aliased in mise.toml, so "3.15" resolves.
|
|
83
|
+
env:
|
|
84
|
+
MISE_PYTHON_VERSION: ${{ matrix.python-version }}
|
|
85
|
+
# pre-commit too, unlike the lint job: this one never runs it.
|
|
86
|
+
MISE_DISABLE_TOOLS: pre-commit,tmux
|
|
87
|
+
|
|
88
|
+
steps:
|
|
89
|
+
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
90
|
+
with:
|
|
91
|
+
persist-credentials: false
|
|
92
|
+
|
|
93
|
+
- name: Set up mise
|
|
94
|
+
uses: jdx/mise-action@3c2e0cf82a5b2e5249f0d3635a4d83d0ae861518 # v4.2.5
|
|
95
|
+
with:
|
|
96
|
+
install: true
|
|
97
|
+
cache: true
|
|
98
|
+
# The default key ignores MISE_PYTHON_VERSION, so all six jobs would
|
|
99
|
+
# share one entry holding a single Python.
|
|
100
|
+
cache_key: "{{default}}-{{env.MISE_PYTHON_VERSION}}"
|
|
101
|
+
|
|
102
|
+
- name: Cache uv downloads
|
|
103
|
+
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
|
|
104
|
+
with:
|
|
105
|
+
path: ~/.cache/uv
|
|
106
|
+
key: uv-${{ runner.os }}-${{ matrix.python-version }}-${{ hashFiles('uv.lock') }}
|
|
107
|
+
restore-keys: uv-${{ runner.os }}-${{ matrix.python-version }}-
|
|
108
|
+
|
|
109
|
+
- name: Test with tox
|
|
110
|
+
run: mise run test-all
|
|
111
|
+
|
|
112
|
+
coverage:
|
|
113
|
+
name: Coverage
|
|
114
|
+
runs-on: ubuntu-latest
|
|
115
|
+
|
|
116
|
+
# The tox matrix runs pytest without --cov, so the fail_under floor in
|
|
117
|
+
# pyproject.toml would otherwise be a local convention nothing enforces.
|
|
118
|
+
# One interpreter is enough: the floor is about the suite, not the matrix.
|
|
119
|
+
env:
|
|
120
|
+
MISE_PYTHON_VERSION: "3.13"
|
|
121
|
+
MISE_DISABLE_TOOLS: pre-commit,tmux
|
|
122
|
+
|
|
123
|
+
steps:
|
|
124
|
+
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
125
|
+
with:
|
|
126
|
+
persist-credentials: false
|
|
127
|
+
|
|
128
|
+
- name: Set up mise
|
|
129
|
+
uses: jdx/mise-action@3c2e0cf82a5b2e5249f0d3635a4d83d0ae861518 # v4.2.5
|
|
130
|
+
with:
|
|
131
|
+
install: true
|
|
132
|
+
cache: true
|
|
133
|
+
cache_key: "coverage-{{default}}-{{env.MISE_PYTHON_VERSION}}"
|
|
134
|
+
|
|
135
|
+
- name: Cache uv downloads
|
|
136
|
+
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
|
|
137
|
+
with:
|
|
138
|
+
path: ~/.cache/uv
|
|
139
|
+
key: uv-${{ runner.os }}-coverage-${{ hashFiles('uv.lock') }}
|
|
140
|
+
restore-keys: uv-${{ runner.os }}-coverage-
|
|
141
|
+
|
|
142
|
+
- name: Install dependencies
|
|
143
|
+
run: uv sync --all-extras --locked
|
|
144
|
+
|
|
145
|
+
- name: Run the suite with coverage
|
|
146
|
+
run: mise run test
|
|
147
|
+
|
|
148
|
+
docs:
|
|
149
|
+
name: Docs
|
|
150
|
+
runs-on: ubuntu-latest
|
|
151
|
+
|
|
152
|
+
# Sphinx reports a missing reference or an unresolvable include as a warning and publishes
|
|
153
|
+
# anyway, so a plain build would pass on exactly the failures worth catching. -W is the point
|
|
154
|
+
# of this job; conf.py suppresses the one warning class Pygments cannot help with.
|
|
155
|
+
env:
|
|
156
|
+
MISE_PYTHON_VERSION: "3.13"
|
|
157
|
+
MISE_DISABLE_TOOLS: pre-commit,tmux
|
|
158
|
+
|
|
159
|
+
steps:
|
|
160
|
+
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
161
|
+
with:
|
|
162
|
+
persist-credentials: false
|
|
163
|
+
|
|
164
|
+
- name: Set up mise
|
|
165
|
+
uses: jdx/mise-action@3c2e0cf82a5b2e5249f0d3635a4d83d0ae861518 # v4.2.5
|
|
166
|
+
with:
|
|
167
|
+
install: true
|
|
168
|
+
cache: true
|
|
169
|
+
cache_key: "docs-{{default}}-{{env.MISE_PYTHON_VERSION}}"
|
|
170
|
+
|
|
171
|
+
- name: Cache uv downloads
|
|
172
|
+
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
|
|
173
|
+
with:
|
|
174
|
+
path: ~/.cache/uv
|
|
175
|
+
key: uv-${{ runner.os }}-docs-${{ hashFiles('docs/requirements.txt') }}
|
|
176
|
+
restore-keys: uv-${{ runner.os }}-docs-
|
|
177
|
+
|
|
178
|
+
- name: Build the docs
|
|
179
|
+
run: mise run build-docs
|
|
180
|
+
|
|
181
|
+
zizmor:
|
|
182
|
+
name: Audit workflows (zizmor)
|
|
183
|
+
runs-on: ubuntu-latest
|
|
184
|
+
permissions:
|
|
185
|
+
contents: read
|
|
186
|
+
actions: read # zizmor-action reads workflow definitions
|
|
187
|
+
security-events: write # upload SARIF results to code scanning
|
|
188
|
+
steps:
|
|
189
|
+
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
190
|
+
with:
|
|
191
|
+
persist-credentials: false
|
|
192
|
+
|
|
193
|
+
- uses: zizmorcore/zizmor-action@3dc1ecc9bcb9e94e9b2c709687979e1298497054 # v0.6.2
|
|
@@ -78,9 +78,11 @@ docs/_build/
|
|
|
78
78
|
# macOS
|
|
79
79
|
.DS_Store
|
|
80
80
|
|
|
81
|
-
#
|
|
82
|
-
|
|
83
|
-
|
|
81
|
+
# Local tool config
|
|
82
|
+
mise.local.toml
|
|
83
|
+
|
|
84
|
+
# Local agent and tracker state (AGENTS.md and CLAUDE.md are tracked)
|
|
85
|
+
CLAUDE.local.md
|
|
84
86
|
.claude/
|
|
85
87
|
.beans.yml
|
|
86
88
|
.beans/
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
#MISE description="tmux dev session: agent, test watcher and a free shell"
|
|
3
|
+
# raw: without it mise sits between the terminal and tmux, and tmux won't attach without a tty.
|
|
4
|
+
#MISE raw=true
|
|
5
|
+
# ------------------------------------------------------------
|
|
6
|
+
# dev — 3-pane tmux session for django-tailwind-cli
|
|
7
|
+
#
|
|
8
|
+
# +----------------+----------------+
|
|
9
|
+
# | | test-watch |
|
|
10
|
+
# | claude +----------------+
|
|
11
|
+
# | --continue | shell |
|
|
12
|
+
# +----------------+----------------+
|
|
13
|
+
#
|
|
14
|
+
# Verbs: stop, restart, status. Bare `mise run dev` starts or attaches.
|
|
15
|
+
# ------------------------------------------------------------
|
|
16
|
+
set -euo pipefail
|
|
17
|
+
|
|
18
|
+
PROJECT_DIR="${MISE_PROJECT_ROOT:-$PWD}"
|
|
19
|
+
SELF="$PROJECT_DIR/.mise/tasks/dev"
|
|
20
|
+
SESSION="${TAILWIND_TMUX_SESSION:-tailwind-cli}"
|
|
21
|
+
# One tmux server per project instead of the shared per-user one. Two reasons, both observed: a session
|
|
22
|
+
# destroyed in one project could drop the client into another project's session (with
|
|
23
|
+
# `detach-on-destroy off`, which is the default in this user's ~/.tmux.conf), and `kill-server` was global.
|
|
24
|
+
# Named after the project, so `tmux -L tailwind-cli ls` reaches it from a plain shell.
|
|
25
|
+
SERVER="${TAILWIND_TMUX_SERVER:-tailwind-cli}"
|
|
26
|
+
tm() { tmux -L "$SERVER" "$@"; }
|
|
27
|
+
# `exec` needs a program, not a shell function, so the three exec lines below spell tmux out. The wrapper
|
|
28
|
+
# stays for everything else: every other call must reach this socket and no other.
|
|
29
|
+
|
|
30
|
+
session_exists() { tm has-session -t "=$SESSION" 2>/dev/null; }
|
|
31
|
+
|
|
32
|
+
# start(), restart() and exec_attach() all refuse a foreign server the same way, so it is said once.
|
|
33
|
+
refuse_foreign_tmux() {
|
|
34
|
+
echo "This shell is inside another tmux server (${TMUX%%,*})."
|
|
35
|
+
echo "Detach there first (prefix d), then run 'mise run dev' again."
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
# Whether this shell sits inside *our* server. A session on a different server can neither be switched to
|
|
39
|
+
# nor be the one restart() is about to kill, and `$TMUX` names the socket the same way tmux reports it
|
|
40
|
+
# (measured: both are the resolved absolute path).
|
|
41
|
+
in_our_server() {
|
|
42
|
+
[[ -n "${TMUX:-}" && "${TMUX%%,*}" == "$(tm display-message -p '#{socket_path}' 2>/dev/null)" ]]
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
in_foreign_tmux() { [[ -n "${TMUX:-}" ]] && ! in_our_server; }
|
|
46
|
+
|
|
47
|
+
# Which panes hold services is recorded at start (@service-panes), not guessed here:
|
|
48
|
+
# the watcher runs through mise and uv, so the pane reports "uv" rather than ptw, and pane titles
|
|
49
|
+
# get rewritten by anything that emits an OSC title sequence.
|
|
50
|
+
service_panes() {
|
|
51
|
+
local ids
|
|
52
|
+
ids="$(tm show-options -t "$SESSION" -v @service-panes 2>/dev/null || true)"
|
|
53
|
+
[[ -z "$ids" ]] && return 0
|
|
54
|
+
tm list-panes -s -t "=$SESSION" -F '#{pane_id} #{pane_current_command}' |
|
|
55
|
+
awk -v ids=" $ids " 'index(ids, " " $1 " ") && $2 !~ /^-?(zsh|bash|fish|sh)$/ { print $1 }'
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
start() {
|
|
59
|
+
if session_exists; then
|
|
60
|
+
exec_attach
|
|
61
|
+
fi
|
|
62
|
+
|
|
63
|
+
# Refuse *before* building anything. exec_attach would refuse too, but only after the session and its
|
|
64
|
+
# services are up — leaving a started session behind and still exiting non-zero.
|
|
65
|
+
if in_foreign_tmux; then
|
|
66
|
+
refuse_foreign_tmux
|
|
67
|
+
return 1
|
|
68
|
+
fi
|
|
69
|
+
|
|
70
|
+
local main rtop rbot
|
|
71
|
+
main="$(tm new-session -d -s "$SESSION" -n dev -c "$PROJECT_DIR" \
|
|
72
|
+
-x "$(tput cols 2>/dev/null || echo 200)" \
|
|
73
|
+
-y "$(tput lines 2>/dev/null || echo 50)" \
|
|
74
|
+
-P -F '#{pane_id}')"
|
|
75
|
+
rtop="$(tm split-window -h -t "$main" -c "$PROJECT_DIR" -P -F '#{pane_id}')"
|
|
76
|
+
rbot="$(tm split-window -v -t "$rtop" -c "$PROJECT_DIR" -P -F '#{pane_id}')"
|
|
77
|
+
tm set-window-option -t "$SESSION:dev" main-pane-width 50%
|
|
78
|
+
tm select-layout -t "$SESSION:dev" main-vertical
|
|
79
|
+
|
|
80
|
+
tm select-pane -t "$main" -T "claude"
|
|
81
|
+
tm select-pane -t "$rtop" -T "test-watch"
|
|
82
|
+
tm select-pane -t "$rbot" -T "shell"
|
|
83
|
+
|
|
84
|
+
# So a key binding can stop whichever dev session it is in: run-shell -b '#{@dev-stop}'
|
|
85
|
+
# Independent of the per-project server: ending this session must return the terminal to its shell,
|
|
86
|
+
# never move it into whatever else the server holds.
|
|
87
|
+
tm set-option -t "$SESSION" detach-on-destroy on
|
|
88
|
+
# Quoted: the documented consumer is `run-shell -b '#{@dev-stop}'`, which hands the string to
|
|
89
|
+
# /bin/sh, so an unquoted checkout path with a space in it would not resolve.
|
|
90
|
+
tm set-option -t "$SESSION" @dev-stop "'$SELF' stop"
|
|
91
|
+
tm set-option -t "$SESSION" @service-panes "$rtop"
|
|
92
|
+
|
|
93
|
+
# send-keys, not a pane command: real interactive shell, and the pane survives the exit.
|
|
94
|
+
# `|| claude` because --continue exits non-zero when this directory has no conversation yet --
|
|
95
|
+
# a fresh clone, or a project whose sessions live under a different CLAUDE_CONFIG_DIR. The
|
|
96
|
+
# trade-off: leaving claude with a non-zero status starts it once more.
|
|
97
|
+
tm send-keys -t "$main" 'claude --continue || claude' C-m
|
|
98
|
+
tm send-keys -t "$rtop" 'mise run test-watch' C-m
|
|
99
|
+
|
|
100
|
+
tm select-pane -t "$main"
|
|
101
|
+
exec_attach
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
exec_attach() {
|
|
105
|
+
# Inside this project's own server switching still works — another window, say.
|
|
106
|
+
if in_our_server; then
|
|
107
|
+
exec tmux -L "$SERVER" switch-client -t "=$SESSION"
|
|
108
|
+
fi
|
|
109
|
+
# From a different project's server it cannot: switch-client does not cross servers, and a nested
|
|
110
|
+
# attach would double every key binding. Ask for a detach rather than producing that.
|
|
111
|
+
# Not in_foreign_tmux: the branch above already exec'd when the server was ours, so reaching this
|
|
112
|
+
# line is itself the "not ours" half of that predicate.
|
|
113
|
+
if [[ -n "${TMUX:-}" ]]; then
|
|
114
|
+
refuse_foreign_tmux
|
|
115
|
+
return 1
|
|
116
|
+
fi
|
|
117
|
+
# Escape hatch: ITERM_CC=0 mise run dev
|
|
118
|
+
if [[ "${TERM_PROGRAM:-}" == "iTerm.app" && "${ITERM_CC:-1}" == "1" ]]; then
|
|
119
|
+
exec tmux -L "$SERVER" -CC attach -t "=$SESSION"
|
|
120
|
+
fi
|
|
121
|
+
exec tmux -L "$SERVER" attach -t "=$SESSION"
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
stop() {
|
|
125
|
+
if ! session_exists; then
|
|
126
|
+
echo "Session '$SESSION' is not running."
|
|
127
|
+
return 0
|
|
128
|
+
fi
|
|
129
|
+
|
|
130
|
+
local pane i
|
|
131
|
+
for pane in $(service_panes); do
|
|
132
|
+
tm send-keys -t "$pane" C-c
|
|
133
|
+
done
|
|
134
|
+
for i in $(seq 200); do
|
|
135
|
+
[[ -z "$(service_panes)" ]] && break
|
|
136
|
+
sleep 0.05
|
|
137
|
+
done
|
|
138
|
+
|
|
139
|
+
tm kill-session -t "=$SESSION"
|
|
140
|
+
echo "Session '$SESSION' stopped."
|
|
141
|
+
# No pkill counterpart here: pytest is a child of ptw and dies with it. Measured after every
|
|
142
|
+
# stop in this session — nothing survived the pane.
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
restart() {
|
|
146
|
+
# Refuse before stop() runs, not after: start() has this same guard, but by the time it refuses
|
|
147
|
+
# the session is already dead — restart from a foreign server destroyed it and rebuilt nothing.
|
|
148
|
+
if in_foreign_tmux; then
|
|
149
|
+
refuse_foreign_tmux
|
|
150
|
+
return 1
|
|
151
|
+
fi
|
|
152
|
+
# kill-session would SIGHUP this very shell before start() runs.
|
|
153
|
+
# `in_our_server` is not decoration: from a plain shell `#S` still reports our session (measured),
|
|
154
|
+
# so the name comparison on its own would refuse every restart from a normal terminal.
|
|
155
|
+
if in_our_server && [[ "$(tm display-message -p '#S')" == "$SESSION" ]]; then
|
|
156
|
+
echo "Run restart from outside '$SESSION' — stopping it kills this shell."
|
|
157
|
+
return 1
|
|
158
|
+
fi
|
|
159
|
+
stop
|
|
160
|
+
start
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
case "${1:-}" in
|
|
164
|
+
stop) stop ;;
|
|
165
|
+
restart) restart ;;
|
|
166
|
+
status) session_exists && echo "running" || echo "stopped" ;;
|
|
167
|
+
*) start ;;
|
|
168
|
+
esac
|
|
@@ -1,6 +1,77 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
-
##
|
|
3
|
+
## 4.8.0 (2026-08-30)
|
|
4
|
+
|
|
5
|
+
### 💥 Dependencies
|
|
6
|
+
- **`django-typer` replaced by `django-click`**, which drops `typer` from the dependency tree entirely. django-typer caps `typer<0.26.0`, `click<8.5` and `django<6.2`; django-click requires only `click>=7.1` with no upper bound, so those caps no longer reach into your environment. The nine subcommands, their options and the `runserver` passthrough are unchanged, `call_command` still accepts options both as keywords and as strings, and `CommandError` still reaches a programmatic caller. Help text is now rendered by click rather than by typer's markdown mode, so backticks in `--help` appear literally.
|
|
7
|
+
- **`call_command("tailwind", ..., verbosity=0)` no longer raises `TypeError`.** Django's own command options were rejected by the command group, which made the command unusable from a script that passes them — as deploy scripts and other packages' test suites routinely do. `stdout` and `stderr` are honoured rather than merely accepted, so a caller can capture the output.
|
|
8
|
+
- **An option meant for another subcommand is refused instead of ignored.** `call_command("tailwind", "config", verbose=True)` used to run and do nothing with `verbose`; it now raises `TypeError`, as Django does for an option a command does not have.
|
|
9
|
+
- **`--skip-checks` and `--force-color` work again**, on the group and on every subcommand, as they do for any other Django management command.
|
|
10
|
+
- **`manage.py tailwind` with no subcommand exits 2** rather than 1, click's code for a usage error. It still prints the help text.
|
|
11
|
+
- **`manage.py tailwind --version` reports this package's version**, not Django's.
|
|
12
|
+
|
|
13
|
+
### 🎯 New Features
|
|
14
|
+
- **A source CSS inside `STATICFILES_DIRS` is reported by the system check `django_tailwind_cli.W001`** instead of failing on deploy. Such a file is collected by `collectstatic`, and a manifest storage backend then cannot resolve the `@import "tailwindcss";` it contains. It stays a warning, not an error — without a manifest backend the file is merely published alongside the build output — and `SILENCED_SYSTEM_CHECKS` turns it off.
|
|
15
|
+
- **Hand edits to the managed `source.css` no longer vanish silently**: the command says what it is about to replace, keeps your version as `source.css.bak`, and points at `TAILWIND_CLI_SRC_CSS` as the way to own the file. Toggling DaisyUI or a changed set of auto-`@source`d apps does not trip it — only content this library could not have written does.
|
|
16
|
+
|
|
17
|
+
### 🐛 Bug Fixes
|
|
18
|
+
- **`TAILWIND_CLI_CSS_MAP` works end to end**: the source CSS was written for the first entry only and `tailwind setup` built only that one, so a multi-entry project came out of setup with one stylesheet and the next `build` failed on the missing second input. Both iterate every entry now.
|
|
19
|
+
- **Two `TAILWIND_CLI_CSS_MAP` entries writing one output file are rejected** instead of quietly building only one of them: the first build wrote the file, the freshness check then found the second entry up to date, and it was reported as built without ever having run. Different spellings of the same path (`./out.css` next to `out.css`) count as the same destination.
|
|
20
|
+
- **A missing setting reads as a configuration error, not an unexpected one**: `get_config` raises `ConfigurationError` now, so `STATICFILES_DIRS is empty` arrives with the `STATICFILES_DIRS = [BASE_DIR / 'assets']` hint and one line of output instead of `❌ Unexpected error` and a traceback. It subclasses `ValueError`, so code calling `get_config()` directly and catching one is unaffected. Through a management command it arrives as a `CommandError`, as every other command failure does.
|
|
21
|
+
- **`tailwind setup` uses the same code as `tailwind build`**: it wrote the source CSS and ran the first build with its own copies, so it ignored `TAILWIND_CLI_AUTO_SOURCE_EXTERNAL_APPS` and `TAILWIND_CLI_AUTOMATIC_MINIFY`, never wrote the managed directory's `.gitignore`, ran the build from the shell's working directory rather than `BASE_DIR`, and exited 0 after reporting that the build had failed. In exchange it now brings an existing `source.css` to the state a build expects rather than leaving it alone — announced, and with a `.css.bak`.
|
|
22
|
+
- **An `@source` line you added yourself is no longer deleted in silence**: the check for "did we write this file" accepted any `@source` directive as its own, so widening template discovery by hand in the managed `source.css` lost the line on the next build, with no warning and no backup. Position decides now — the library only ever writes `@source` below its own generated comment.
|
|
23
|
+
- **Command errors are reported the way they were meant to be**: the error decorator sat above `@app.command`, so typer registered the undecorated function and none of it ever ran. A failing command showed a bare traceback instead of the message and the suggested fix. It now prints the hint to stderr and lets the failure continue: a user error as a `CommandError`, which Django renders and a caller using `call_command` can catch, and a bug with its traceback intact. A failing Tailwind build called `sys.exit` and bypassed all of this; it raises now too.
|
|
24
|
+
- **A `TAILWIND_CLI_PATH` pointing straight at a binary no longer ignores `TAILWIND_CLI_VERSION`**: that filename carries no version, so a bump used to reuse the old binary in silence. `build`, `watch` and `runserver` read the version from the binary now and warn on a mismatch rather than replacing a file you placed there. A managed download is unaffected: its version is in its filename. `download_cli` still replaces a binary you supplied — that is what it was asked to do — but names the file first.
|
|
25
|
+
- **A CLI binary that appears mid-process is seen again**: an existence check was cached for five seconds behind a module-level dict, so with `TAILWIND_CLI_AUTOMATIC_DOWNLOAD = False` a build right after placing the binary still reported it missing. The cache served one call site and is gone.
|
|
26
|
+
- **`tailwind runserver` says when there is no `manage.py` at `BASE_DIR`** instead of starting both subprocesses and reporting that the server is up, only for it to die a moment later with a bare exit code. Layouts that keep `manage.py` elsewhere are pointed at running the two halves separately.
|
|
27
|
+
- **`TAILWIND_CLI_SRC_CSS` expands a leading `~`**, as `TAILWIND_CLI_PATH` already did. It used to become a directory named `~` below `BASE_DIR`. `TAILWIND_CLI_CSS_MAP` sources go through the same resolution now.
|
|
28
|
+
- **A prefixed `STATICFILES_DIRS` entry may be a list**, not only a tuple. Django's own `FileSystemFinder` accepts both; `STATICFILES_DIRS[0]` in list form raised a `TypeError` before reaching any Tailwind code.
|
|
29
|
+
- **The HTTP layer closes the error it catches.** `urllib`'s `HTTPError` is the response object and
|
|
30
|
+
owns an open file handle — one it allocates itself when the server sent no body. All three places
|
|
31
|
+
that wrapped it kept it alive through the exception chain without closing it, which Python 3.14
|
|
32
|
+
reports as `ResourceWarning: Implicitly cleaning up <HTTPError ...>` on a failed download or
|
|
33
|
+
version lookup.
|
|
34
|
+
|
|
35
|
+
### 🛠️ Developer Experience
|
|
36
|
+
- **CI builds the docs with warnings as errors**: Sphinx reports a missing reference or an unresolvable include as a warning and publishes anyway, so a plain build would pass on exactly the failures worth catching. `mise run build-docs` runs the same check locally.
|
|
37
|
+
- **One download stub for the whole suite**: `tests/helpers.py` holds `write_fake_cli`, replacing 29 copies of the same fixture across four test modules. Net −290 lines.
|
|
38
|
+
- **Coverage has a floor**: the suite fails below 90%, and branch coverage is on, so both sides of a condition count. A `coverage` job in CI enforces it — the tox matrix runs pytest without `--cov`, so the floor would otherwise have been a local convention.
|
|
39
|
+
- **TestPyPI is a gate for the PyPI release again**: `publish-to-pypi` waits for `publish-to-testpypi` instead of running beside it, so a broken artifact fails somewhere reversible first. The TestPyPI step already sets `skip-existing`, so a re-run does not block on an already-published version.
|
|
40
|
+
- **Shared test fixtures moved into `tests/conftest.py`**: `bypass_autoreload` and `fake_project_settings` replace four copies of the autoreload bypass and three copies of the settings setup. Modules opt in with `@pytest.mark.usefixtures(...)`.
|
|
41
|
+
- **The linters run in CI again**: a `lint` job runs the full pre-commit suite (ruff, basedpyright, uv-secure, the upgrade hooks) on every pull request. It had been dropped in 2022 because pyright could not resolve imports without the project venv, which `mise run bootstrap` now provides.
|
|
42
|
+
|
|
43
|
+
### 🔧 Technical Improvements
|
|
44
|
+
- **No subprocess during template rendering**: with `TAILWIND_CLI_USE_SYSTEM_BINARY` and a pinned version, the version check ran inside `get_config()`, which `{% tailwind_css %}` calls on every render — so the first request of every worker process executed `tailwindcss --help`. It runs on the command path now.
|
|
45
|
+
- **License metadata uses the SPDX form**: `license = "MIT"` plus `license-files`, so PyPI shows `MIT` instead of the whole licence text pasted into the metadata field. The `License :: OSI Approved` classifier is gone, which PyPI rejects alongside an SPDX expression.
|
|
46
|
+
- **`_validate_css_settings` reads settings the way the rest of `config.py` does**, through `getattr(..., None)` rather than `hasattr` plus a second read. No behaviour change; the two idioms were verified to agree for an unset, `None`, empty and populated setting alike.
|
|
47
|
+
- **The test suite no longer reaches the network**: `tests/conftest.py` fails any test that resolves a hostname, isolates the version cache per test, and answers the release lookup from a fixture. Until now the suite overwrote the machine-wide version cache that `manage.py tailwind` itself reads, and one setup test downloaded a real Tailwind binary into the source tree on every new version.
|
|
48
|
+
|
|
49
|
+
### 📚 Documentation
|
|
50
|
+
- **`docs/development.md` is `docs/workflow.md`**, titled "Daily Workflow". It documents using the package day to day, not contributing to it, and sat next to "Contributing" in the sidebar saying otherwise.
|
|
51
|
+
- **The base template page shows the shipped file**, included rather than transcribed. The hand-copied version had drifted from `tailwind_cli/base.html`.
|
|
52
|
+
- **The README command table lists all nine subcommands**: `optimize`, `download_cli` and `remove_cli` were missing, although the table reads as the full list and `troubleshoot` points users at `download_cli`.
|
|
53
|
+
- **`CONTRIBUTING.md`**: development setup, test commands, commit and changelog conventions, and what makes an issue or pull request easy to act on. Linked from the README and published in the docs.
|
|
54
|
+
- **`AGENTS.md` is tracked**: the repository conventions coding agents need live in the repo now instead of an ignored local file. `CLAUDE.md` is a one-line pointer at it.
|
|
55
|
+
- **WhiteNoise page**: a sample configuration and the two traps that only show up on deploy — a `collectstatic` that runs before `tailwind build` fails at render time, not at build time, and a source CSS inside `STATICFILES_DIRS` breaks `collectstatic` outright.
|
|
56
|
+
- **`troubleshoot` covers the deployment failure**: a `collectstatic` that runs before `tailwind build` leaves the manifest without an entry, and the guide now carries that error string and the fix. `setup` spells out the ordering too.
|
|
57
|
+
- **DaisyUI is explained once**: the "Configuration Patterns" section pointed at the setting entry's explanation instead of repeating it, keeping only the manual-configuration and theme examples that the entry does not cover.
|
|
58
|
+
- **`template_tags.md` showed `/static/css/styles.css`**, a path no configuration produces. It is `css/tailwind.css`.
|
|
59
|
+
- **Cross-references to settings resolve again**: `myst_heading_anchors` was never enabled, so every `settings.md#tailwind_cli_…` link in the docs pointed at a fragment that does not exist. Three links had been broken since they were written.
|
|
60
|
+
|
|
61
|
+
## 4.7.0 (2026-08-15)
|
|
62
|
+
|
|
63
|
+
### 🎯 New Features
|
|
64
|
+
- **Django 6.1 support**: added to the test matrix and the trove classifiers.
|
|
65
|
+
|
|
66
|
+
### 🛠️ Developer Experience
|
|
67
|
+
- **Switch dev tooling from `just` to `mise`**: Python 3.10–3.15, `uv`, and `pre-commit` are now pinned in `mise.toml`. Local tasks run via `mise run …`; CI uses `jdx/mise-action`.
|
|
68
|
+
|
|
69
|
+
### 📚 Documentation
|
|
70
|
+
- **Plainer README**: dropped the emoji headings and marketing register, and merged the overlapping feature sections into one. Same coverage, ~20% shorter.
|
|
71
|
+
- **Accurate `tailwind setup` help**: `--help` no longer calls the command interactive. It lists the eight checks it actually runs and notes that it prompts for nothing.
|
|
72
|
+
|
|
73
|
+
### 🔧 Technical Improvements
|
|
74
|
+
- **Dependency refresh**: clears the advisories `uv-secure` flagged in the old lock. `pytest-django` 4.14 renamed `SettingsWrapper` to `Settings`, so the dev floor moved to 4.14.
|
|
4
75
|
|
|
5
76
|
## 4.6.2 (2026-05-14)
|
|
6
77
|
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
# Contributing
|
|
2
|
+
|
|
3
|
+
Contributions are welcome — bug reports, documentation fixes, and pull requests alike. This project
|
|
4
|
+
follows the
|
|
5
|
+
[Django Commons Code of Conduct](https://github.com/django-commons/membership/blob/main/CODE_OF_CONDUCT.md).
|
|
6
|
+
|
|
7
|
+
## Before you open an issue or a pull request
|
|
8
|
+
|
|
9
|
+
This project is maintained in spare time. Some weeks are quiet, some weeks the queue is longer than
|
|
10
|
+
the time available for it. A few expectations keep that workable:
|
|
11
|
+
|
|
12
|
+
- **One topic per issue, one change per pull request.** Small and focused gets handled quickly.
|
|
13
|
+
A submission that bundles four unrelated things waits until there is time for all four.
|
|
14
|
+
- **Write it so it can be understood on the first read.** What you did, what you expected, what
|
|
15
|
+
happened instead. An unclear report gets closed rather than investigated — reconstructing the
|
|
16
|
+
missing half costs more time than the fix does.
|
|
17
|
+
- **AI-assisted work is fine, unreviewed AI output is not.** You are the author of what you submit.
|
|
18
|
+
If you cannot explain every line of it, it is not ready.
|
|
19
|
+
- **A quick answer is not an invitation.** A first issue fixed within the hour was well written,
|
|
20
|
+
not proof of spare capacity waiting for the next five.
|
|
21
|
+
- **The subject line does most of the work.** Say what is broken, not that something is.
|
|
22
|
+
|
|
23
|
+
If you are unsure whether something warrants an issue, ask first: open a
|
|
24
|
+
[discussion](https://github.com/django-commons/django-tailwind-cli/discussions) or mail
|
|
25
|
+
<oliver@andrich.me>. Questions about direction are welcome and cheaper than a rejected pull request.
|
|
26
|
+
|
|
27
|
+
## Reporting a bug
|
|
28
|
+
|
|
29
|
+
Run the diagnostics and paste their output — they cover most of what would otherwise be a round of
|
|
30
|
+
questions:
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
python manage.py tailwind config # resolved paths and settings
|
|
34
|
+
python manage.py tailwind troubleshoot # common misconfigurations
|
|
35
|
+
python manage.py tailwind build --verbose
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
Add your OS, your Python and Django versions, the steps to reproduce, and the full traceback if
|
|
39
|
+
there is one.
|
|
40
|
+
|
|
41
|
+
## Development setup
|
|
42
|
+
|
|
43
|
+
The only prerequisite is [mise](https://mise.jdx.dev/); it provisions Python 3.10–3.15, `uv`, and
|
|
44
|
+
`pre-commit` from `mise.toml`.
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
git clone git@github.com:django-commons/django-tailwind-cli.git
|
|
48
|
+
cd django-tailwind-cli
|
|
49
|
+
|
|
50
|
+
mise install
|
|
51
|
+
mise run bootstrap
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
## Tests and checks
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
mise run test # pytest with coverage on the default interpreter
|
|
58
|
+
mise run test-all # the full Python/Django matrix via tox
|
|
59
|
+
mise run lint # pre-commit: ruff, basedpyright, uv-secure, upgrade hooks
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
`mise run test` and `mise run lint` both have to pass before a pull request is ready. `test-all`
|
|
63
|
+
takes a while; CI runs the matrix on every pull request, so running it locally is optional unless
|
|
64
|
+
you are touching something version-specific.
|
|
65
|
+
|
|
66
|
+
One of the lint hooks, `uv-secure`, checks the lock file against published security advisories. If
|
|
67
|
+
it fails on a dependency your change never touched, a new advisory appeared and the lock needs a
|
|
68
|
+
bump — mention it in the pull request instead of working around it.
|
|
69
|
+
|
|
70
|
+
## An optional dev session
|
|
71
|
+
|
|
72
|
+
`mise run dev` opens a three-pane tmux session — `claude --continue` on the left, `pytest-watcher`
|
|
73
|
+
running the suite on every save top right, a shell bottom right — on a tmux server of its own named
|
|
74
|
+
after the project, so stopping it can never disturb another project's session.
|
|
75
|
+
|
|
76
|
+
```bash
|
|
77
|
+
mise run dev # start, or attach if it is already running
|
|
78
|
+
mise run dev stop # shut it down
|
|
79
|
+
mise run dev restart # from outside the session
|
|
80
|
+
mise run dev status
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
Entirely optional — nothing in the project depends on it. The watcher is also available on its own
|
|
84
|
+
as `mise run test-watch`; it runs without coverage so the feedback stays fast, and `mise run test`
|
|
85
|
+
remains the command that enforces it.
|
|
86
|
+
|
|
87
|
+
## Commit messages
|
|
88
|
+
|
|
89
|
+
[Conventional Commits](https://www.conventionalcommits.org/) in English, with a scope:
|
|
90
|
+
|
|
91
|
+
```
|
|
92
|
+
feat(management): add purge command for cleaning CSS
|
|
93
|
+
fix(config): handle prefixed staticfile directories
|
|
94
|
+
chore(deps): bump django-click to 2.5.0
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
Use the scope that names the area you touched. `config`, `management`, `build`, `watch`,
|
|
98
|
+
`runserver`, `download`, `http`, `docs`, `tests`, `ci`, and `deps` are the common ones; `git log`
|
|
99
|
+
shows the rest.
|
|
100
|
+
|
|
101
|
+
Keep the message short — a title plus one or two sentences on the *why*. The diff already shows
|
|
102
|
+
what changed, and test counts or coverage numbers belong in the pull request, not in the history.
|
|
103
|
+
|
|
104
|
+
Commit under your own name and without bot co-author trailers. Whatever tools you used, the change
|
|
105
|
+
is yours.
|
|
106
|
+
|
|
107
|
+
## Changelog
|
|
108
|
+
|
|
109
|
+
User-facing changes get an entry in `CHANGELOG.md` under `## Unreleased`, in one of the categories
|
|
110
|
+
the file already uses (Breaking Changes, New Features, Bug Fixes, Developer Experience, and so on).
|
|
111
|
+
One or two bullets, focused on what the change means for users rather than on how it was
|
|
112
|
+
implemented. Internal refactorings that nobody notices from the outside do not need one.
|
|
113
|
+
|
|
114
|
+
## Releases
|
|
115
|
+
|
|
116
|
+
**The major version tracks Tailwind CSS, not this package's own compatibility.** `4.x` supports
|
|
117
|
+
Tailwind 4.x, and the major only moves when Tailwind's does. A change that would be a major
|
|
118
|
+
elsewhere under semver — a dropped dependency, a different exit code — goes into a minor here and
|
|
119
|
+
is called out in the changelog instead.
|
|
120
|
+
|
|
121
|
+
Releasing is a tag. `[tool.hatch.version]` reads the version from git, so `v4.8.0` *is* the
|
|
122
|
+
version. Pushing the tag starts the release rather than finishing it: the workflow builds, uploads
|
|
123
|
+
to TestPyPI, and then waits — the `pypi` environment requires a reviewer, so PyPI publishing needs
|
|
124
|
+
an admin to approve it in the GitHub UI. Do not push a tag and walk away.
|
|
125
|
+
|
|
126
|
+
Rename `## Unreleased` to `## 4.8.0 (YYYY-MM-DD)` before tagging. The release workflow extracts
|
|
127
|
+
that section for the GitHub release notes and fails if it cannot find one, which blocks the whole
|
|
128
|
+
release rather than shipping empty notes.
|
|
129
|
+
|
|
130
|
+
## Pull requests
|
|
131
|
+
|
|
132
|
+
Fork the repository and work on a feature branch. Add tests for new behaviour and update the
|
|
133
|
+
documentation when the change is user-facing — `docs/` is published to
|
|
134
|
+
[Read the Docs](https://django-tailwind-cli.rtfd.io/), and the README is its landing page. New code
|
|
135
|
+
carries type hints; `basedpyright` runs in strict mode. CI runs the same pre-commit hooks that
|
|
136
|
+
`mise run lint` runs, so checking locally first saves you a red pipeline.
|
|
137
|
+
|
|
138
|
+
## License
|
|
139
|
+
|
|
140
|
+
By contributing you agree that your contribution is licensed under the
|
|
141
|
+
[MIT license](https://github.com/django-commons/django-tailwind-cli/blob/main/LICENSE) of this
|
|
142
|
+
project.
|