Skip to content

Commit 47cce4f

Browse files
committed
TAILWIND_CLI_VERSION defaults to 'latest'.
1 parent 6501e3c commit 47cce4f

File tree

7 files changed

+228
-273
lines changed

7 files changed

+228
-273
lines changed

CHANGELOG.md

+6
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
11
# Changelog
22

3+
## 4.1.0
4+
5+
- TAILWIND_CLI_VERSION defaults to "latest" now. In case it is set to this value,
6+
django-tailwind-cli tries to determine which version is the latest version of Tailwind CSS. If it is not able to access the internet, it uses a fallback version defined in django_tailwind_cli.config.FALLBACK_VERSION. Currently,
7+
it is set to 4.0.6.
8+
39
## 4.0.1
410

511
- Various fixes for the documentation.

docs/settings.md

+10-1
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,19 @@ The package can be configured by a few settings, which can be overwritten in the
1111
your project.
1212

1313
`TAILWIND_CLI_VERSION`
14-
: **Default**: `"4.0.0"`
14+
: **Default**: `"latest"`
1515

1616
This defines the version of the CLI and of Tailwind CSS you want to use in your project.
1717

18+
If it is set to `latest`, the management commands try to determine the most recent version of Tailwind CSS by placing a request to GitHub and parse the location header of the redirect. If this is not possible a fallback version is used. This version is defined in the module `django_tailwind_cli.config`.
19+
20+
If you want to pinpoint your setup to certain version of Tailwind CSS, then you can set `TAILWIND_CLI_VERSION`to a fixed version number.
21+
22+
For example:
23+
```python
24+
TAILWIND_CLI_VERSION = "3.4.17"
25+
```
26+
1827
`TAILWIND_CLI_PATH`
1928
: **Default**: `"~/.local/bin/"`
2029

pyproject.toml

-2
Original file line numberDiff line numberDiff line change
@@ -50,8 +50,6 @@ dev = [
5050
"pytest-randomly>=3.15.0",
5151
"pytest>=8.3.3",
5252
"django-stubs[compatible-mypy]>=5.1.1",
53-
"tox>=4.23.2",
54-
"tox-uv>=1.16.0",
5553
]
5654

5755
[build-system]

src/django_tailwind_cli/config.py

+31-2
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,12 @@
44
from pathlib import Path
55
from typing import Optional
66

7+
import requests
78
from django.conf import settings
89
from semver import Version
910

11+
FALLBACK_VERSION = "4.0.6"
12+
1013

1114
@dataclass
1215
class Config:
@@ -49,6 +52,33 @@ def build_cmd(self) -> list[str]:
4952
return result
5053

5154

55+
def get_version() -> tuple[str, Version]:
56+
"""
57+
Retrieves the version of Tailwind CSS specified in the Django settings or fetches the latest
58+
version from the Tailwind CSS GitHub repository.
59+
60+
Returns:
61+
tuple[str, Version]: A tuple containing the version string and the parsed Version object.
62+
63+
Raises:
64+
ValueError: If the TAILWIND_CLI_SRC_REPO setting is None when the version is set to
65+
"latest".
66+
"""
67+
version_str = getattr(settings, "TAILWIND_CLI_VERSION", "latest")
68+
69+
if version_str == "latest":
70+
repo_url = getattr(settings, "TAILWIND_CLI_SRC_REPO", "tailwindlabs/tailwindcss")
71+
if not repo_url:
72+
raise ValueError("TAILWIND_CLI_SRC_REPO must not be None.")
73+
r = requests.get(f"https://github.com/{repo_url}/releases/latest/", timeout=2)
74+
if r.ok and "location" in r.headers:
75+
version_str = r.headers["location"].rstrip("/").split("/")[-1].replace("v", "")
76+
else:
77+
version_str = FALLBACK_VERSION
78+
79+
return version_str, Version.parse(version_str)
80+
81+
5282
def get_config() -> Config:
5383
if settings.STATICFILES_DIRS is None or len(settings.STATICFILES_DIRS) == 0:
5484
raise ValueError("STATICFILES_DIRS is empty. Please add a path to your static files.")
@@ -67,8 +97,7 @@ def get_config() -> Config:
6797
extension = ".exe" if system == "windows" else ""
6898

6999
# Read version from settings
70-
version_str = getattr(settings, "TAILWIND_CLI_VERSION", "4.0.0")
71-
version = Version.parse(version_str)
100+
version_str, version = get_version()
72101

73102
# Determine the full path to the CLI
74103
cli_path = Path(getattr(settings, "TAILWIND_CLI_PATH", "~/.local/bin/") or settings.BASE_DIR)

src/django_tailwind_cli/templatetags/tailwind_cli.py

+2-4
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,11 @@
55
from django import template
66
from django.conf import settings
77

8-
from django_tailwind_cli import config
9-
108
register = template.Library()
119

1210

1311
@register.inclusion_tag("tailwind_cli/tailwind_css.html") # type: ignore
1412
def tailwind_css() -> dict[str, Union[bool, str]]:
1513
"""Template tag to include the css files into the html templates."""
16-
c = config.get_config()
17-
return {"debug": settings.DEBUG, "tailwind_dist_css": str(c.dist_css_base)}
14+
dist_css_base = getattr(settings, "TAILWIND_CLI_DIST_CSS", "css/tailwind.css")
15+
return {"debug": settings.DEBUG, "tailwind_dist_css": str(dist_css_base)}

tests/test_config.py

+54-2
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,67 @@
22

33
import pytest
44
from django.conf import LazySettings
5+
from pytest_django.fixtures import SettingsWrapper
56
from pytest_mock import MockerFixture
67

7-
from django_tailwind_cli.config import get_config
8+
from django_tailwind_cli.config import get_config, get_version
89

910

1011
@pytest.fixture(autouse=True)
11-
def configure_settings(settings: LazySettings):
12+
def configure_settings(
13+
settings: LazySettings,
14+
mocker: MockerFixture,
15+
):
1216
settings.BASE_DIR = Path("/home/user/project")
1317
settings.STATICFILES_DIRS = (settings.BASE_DIR / "assets",)
18+
request_get = mocker.patch("requests.get")
19+
request_get.return_value.headers = {"location": "https://github.com/tailwindlabs/tailwindcss/releases/tag/v4.0.10"}
20+
21+
22+
@pytest.mark.parametrize(
23+
"version_str, version",
24+
[
25+
("4.0.0", (4, 0, 0)),
26+
("3.4.17", (3, 4, 17)),
27+
],
28+
)
29+
def test_get_version(settings: SettingsWrapper, version_str: str, version: tuple[int, int, int]):
30+
settings.TAILWIND_CLI_VERSION = version_str
31+
r_version_str, r_version = get_version()
32+
assert r_version_str == version_str
33+
assert r_version.major == version[0]
34+
assert r_version.minor == version[1]
35+
assert r_version.patch == version[2]
36+
37+
38+
def test_get_version_latest(settings: SettingsWrapper):
39+
r_version_str, r_version = get_version()
40+
assert r_version_str == "4.0.10"
41+
assert r_version.major == 4
42+
assert r_version.minor == 0
43+
assert r_version.patch == 10
44+
45+
46+
def test_get_version_latest_without_proper_http_response(mocker: MockerFixture):
47+
request_get = mocker.patch("requests.get")
48+
request_get.return_value.ok = False
49+
50+
r_version_str, r_version = get_version()
51+
assert r_version_str == "4.0.6"
52+
assert r_version.major == 4
53+
assert r_version.minor == 0
54+
assert r_version.patch == 6
55+
56+
57+
def test_get_version_latest_without_redirect(mocker: MockerFixture):
58+
request_get = mocker.patch("requests.get")
59+
request_get.return_value.headers = {}
60+
61+
r_version_str, r_version = get_version()
62+
assert r_version_str == "4.0.6"
63+
assert r_version.major == 4
64+
assert r_version.minor == 0
65+
assert r_version.patch == 6
1466

1567

1668
def test_default_config():

0 commit comments

Comments
 (0)