Skip to content

Commit eefec06

Browse files
committed
refactor(runserver): replace hand-copied options with typer passthrough
`tailwind runserver` used to declare every runserver / runserver_plus flag as its own typer.Option and ship a translation helper to turn them back into argv — two sources of truth that had already drifted (the wrapper was missing ~7 runserver_plus flags including --extra-file, --reloader-interval and --browser). Use Click's `ignore_unknown_options` + `allow_extra_args` so only `--force-default-runserver` is caught by typer; every other positional and flag falls into `ctx.args` and is forwarded verbatim to the subprocess. Argument parsing goes back to Django where it belongs. Trade-off: `tailwind runserver --help` no longer lists the upstream flags — the docstring now points at `runserver --help` / `runserver_plus --help` for the authoritative list.
1 parent b8a306f commit eefec06

5 files changed

Lines changed: 102 additions & 332 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
- **Type checking**: Switched from `pyright` to `basedpyright` in pre-commit, added `django-stubs` as a dev dependency, and resolved a latent pre-existing baseline so the type checker runs clean on all files.
2323
- **Pre-commit hooks**: Bumped all hooks to their latest releases (pre-commit-hooks 6.0.0, ruff 0.15.10, pyupgrade 3.21.2, django-upgrade 1.30.0, djade 1.9.0, uv-secure 0.17.1).
2424
- **Test coverage**: Raised `config.py` to 100% and `http.py` from 67% to 100%, covering the previously-untested download body (chunked writes with progress callbacks), 200/redirect responses, HTTP 4xx/5xx paths, generic `URLError` branches, and the `NoRedirectHandler` redirect methods.
25+
- **`tailwind runserver` is now a transparent passthrough wrapper**. Instead of declaring every `runserver` / `runserver_plus` flag by hand, the command forwards all unknown options to the underlying Django command. This removes ~120 lines of duplication, fixes the silent gap where several `runserver_plus` flags (`--extra-file`, `--reloader-interval`, `--browser`, …) were not exposed, and stays in sync with future upstream changes automatically. Existing invocations keep working unchanged; the reduced output of `tailwind runserver --help` now points users at `runserver --help` for the full flag list.
2526

2627
## 4.5.1 (2025-12-29)
2728

docs/usage.md

Lines changed: 26 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -32,54 +32,32 @@ python manage.py tailwind watch --noreload
3232

3333
### runserver
3434

35-
Run `python manage.py tailwind runserver` to start the classic Django debug server in parallel to a tailwind watcher process.
36-
37-
```text
38-
Usage: manage.py tailwind runserver
39-
[OPTIONS] [ADDRPORT]
40-
41-
Run the development server with Tailwind CSS CLI in watch mode.
42-
43-
If django-extensions is installed along with this library, this command runs
44-
the runserver_plus command from django-extensions. Otherwise it runs the
45-
default runserver command.
46-
47-
Arguments:
48-
[ADDRPORT] Optional port number, or ipaddr:port
49-
50-
Options:
51-
-6, --ipv6 Tells Django to use an IPv6 address.
52-
--nothreading Tells Django to NOT use threading.
53-
--nostatic Tells Django to NOT automatically serve
54-
static files at STATIC_URL.
55-
--noreload Tells Django to NOT use the auto-reloader.
56-
--skip-checks Skip system checks.
57-
--pdb Drop into pdb shell at the start of any
58-
view. (Requires django-extensions.)
59-
--ipdb Drop into ipdb shell at the start of any
60-
view. (Requires django-extensions.)
61-
--pm Drop into (i)pdb shell if an exception is
62-
raised in a view. (Requires django-
63-
extensions.)
64-
--print-sql Print SQL queries as they're executed.
65-
(Requires django-extensions.)
66-
--print-sql-location Show location in code where SQL query
67-
generated from. (Requires django-
68-
extensions.)
69-
--cert-file TEXT SSL .crt file path. If not provided path
70-
from --key-file will be selected. Either
71-
--cert-file or --key-file must be provided
72-
to use SSL. (Requires django-extensions.)
73-
--key-file TEXT SSL .key file path. If not provided path
74-
from --cert-file will be selected. Either
75-
--cert-file or --key-file must be provided
76-
to use SSL. (Requires django-extensions.)
77-
--force-default-runserver / --no-force-default-runserver
78-
Force the use of the default runserver
79-
command even if django-extensions is
80-
installed. [default: no-force-default-
81-
runserver]
82-
--help Show this message and exit.
35+
Run `python manage.py tailwind runserver` to start the Django debug server in parallel to a tailwind watcher process. If `django-extensions` plus `werkzeug` are installed, `runserver_plus` is used automatically; otherwise the vanilla `runserver` command runs.
36+
37+
This command is a transparent passthrough wrapper: **every** positional argument and option other than the tailwind-specific `--force-default-runserver` is forwarded verbatim to the underlying server command. That includes flags the wrapper itself does not know about (e.g. `runserver_plus`'s `--extra-file`, `--reloader-interval`, `--browser`, `--exclude-pattern`, …).
38+
39+
For the exhaustive list of forwarded flags, run:
40+
41+
```bash
42+
python manage.py runserver --help
43+
python manage.py runserver_plus --help # with django-extensions
44+
```
45+
46+
Examples:
47+
48+
```bash
49+
# Default port (8000)
50+
python manage.py tailwind runserver
51+
52+
# Custom port
53+
python manage.py tailwind runserver 8080
54+
55+
# Forward arbitrary runserver / runserver_plus flags
56+
python manage.py tailwind runserver 0.0.0.0:8000 --noreload
57+
python manage.py tailwind runserver --print-sql --ipdb
58+
59+
# Pin to vanilla runserver even with django-extensions installed
60+
python manage.py tailwind runserver --force-default-runserver
8361
```
8462

8563
### download_cli

src/django_tailwind_cli/management/commands/tailwind.py

Lines changed: 27 additions & 180 deletions
Original file line numberDiff line numberDiff line change
@@ -968,162 +968,57 @@ def remove_cli():
968968
typer.secho(f"Tailwind CSS CLI not found at '{c.cli_path}'.", fg=typer.colors.RED)
969969

970970

971-
@app.command()
971+
@app.command(
972+
context_settings={
973+
"ignore_unknown_options": True,
974+
"allow_extra_args": True,
975+
},
976+
)
972977
def runserver(
973-
addrport: str | None = typer.Argument(
974-
None,
975-
help="Optional port number, or ipaddr:port",
976-
),
978+
ctx: typer.Context,
977979
*,
978-
use_ipv6: bool = typer.Option(
979-
False,
980-
"--ipv6",
981-
"-6",
982-
help="Tells Django to use an IPv6 address.",
983-
),
984-
no_threading: bool = typer.Option(
985-
False,
986-
"--nothreading",
987-
help="Tells Django to NOT use threading.",
988-
),
989-
no_static: bool = typer.Option(
990-
False,
991-
"--nostatic",
992-
help="Tells Django to NOT automatically serve static files at STATIC_URL.",
993-
),
994-
no_reloader: bool = typer.Option(
995-
False,
996-
"--noreload",
997-
help="Tells Django to NOT use the auto-reloader.",
998-
),
999-
skip_checks: bool = typer.Option(
1000-
False,
1001-
"--skip-checks",
1002-
help="Skip system checks.",
1003-
),
1004-
pdb: bool = typer.Option(
1005-
False,
1006-
"--pdb",
1007-
help="Drop into pdb shell at the start of any view. (Requires django-extensions.)",
1008-
),
1009-
ipdb: bool = typer.Option(
1010-
False,
1011-
"--ipdb",
1012-
help="Drop into ipdb shell at the start of any view. (Requires django-extensions.)",
1013-
),
1014-
pm: bool = typer.Option(
1015-
False,
1016-
"--pm",
1017-
help="Drop into (i)pdb shell if an exception is raised in a view. (Requires django-extensions.)",
1018-
),
1019-
print_sql: bool = typer.Option(
1020-
False,
1021-
"--print-sql",
1022-
help="Print SQL queries as they're executed. (Requires django-extensions.)",
1023-
),
1024-
print_sql_location: bool = typer.Option(
1025-
False,
1026-
"--print-sql-location",
1027-
help="Show location in code where SQL query generated from. (Requires django-extensions.)",
1028-
),
1029-
cert_file: str | None = typer.Option(
1030-
None,
1031-
help=(
1032-
"SSL .crt file path. If not provided path from --key-file will be selected. "
1033-
"Either --cert-file or --key-file must be provided to use SSL. "
1034-
"(Requires django-extensions.)"
1035-
),
1036-
),
1037-
key_file: str | None = typer.Option(
1038-
None,
1039-
help=(
1040-
"SSL .key file path. If not provided path from --cert-file will be "
1041-
"selected. Either --cert-file or --key-file must be provided to use SSL. "
1042-
"(Requires django-extensions.)"
1043-
),
1044-
),
1045980
force_default_runserver: bool = typer.Option(
1046981
False,
1047-
help=("Force the use of the default runserver command even if django-extensions is installed. "),
982+
help="Force vanilla runserver even if django-extensions is installed.",
1048983
),
1049984
):
1050985
"""Run Django development server with Tailwind CSS watch mode.
1051986
1052-
This command combines 'tailwind watch' and Django's runserver, providing a
1053-
complete development environment in a single terminal. It automatically:
1054-
- Starts Tailwind CSS in watch mode to rebuild styles on changes
1055-
- Runs Django's development server
1056-
- Manages both processes with proper signal handling
987+
Combines `tailwind watch` and Django's runserver in one terminal, with
988+
signal-clean shutdown of both processes on Ctrl+C. If `django-extensions`
989+
plus `werkzeug` are installed, `runserver_plus` is used by default — pass
990+
`--force-default-runserver` to opt out.
1057991
1058992
\b
1059-
Features:
1060-
- Automatic process management (both stop cleanly with Ctrl+C)
1061-
- Live CSS updates as you edit templates and styles
1062-
- Support for django-extensions runserver_plus (if installed)
1063-
- All standard runserver options are supported
993+
All positional arguments and options other than `--force-default-runserver`
994+
are forwarded verbatim to the underlying server command. That means every
995+
runserver / runserver_plus flag is supported, including ones this wrapper
996+
does not know about:
1064997
1065998
\b
1066-
Examples:
1067-
# Run on default port (8000)
1068999
python manage.py tailwind runserver
1069-
1070-
# Run on custom port
10711000
python manage.py tailwind runserver 8080
1072-
1073-
# Run on specific IP and port
1074-
python manage.py tailwind runserver 0.0.0.0:8000
1075-
1076-
# Run with django-extensions features
1077-
python manage.py tailwind runserver --print-sql
1078-
1079-
# Force default runserver (ignore django-extensions)
1080-
python manage.py tailwind runserver --force-default-runserver
1001+
python manage.py tailwind runserver 0.0.0.0:8000 --noreload
1002+
python manage.py tailwind runserver --print-sql --ipdb
1003+
python manage.py tailwind runserver --extra-file .env --reloader-interval 5
10811004
10821005
\b
1083-
Tips:
1084-
- This replaces the need to run 'tailwind watch' and 'runserver' separately
1085-
- Both processes are managed together - Ctrl+C stops both cleanly
1086-
- Check console output for both Tailwind build status and Django logs
1006+
For the full list of forwarded flags, see:
1007+
python manage.py runserver --help
1008+
python manage.py runserver_plus --help (with django-extensions)
10871009
"""
1088-
if (
1010+
use_plus = (
10891011
importlib.util.find_spec("django_extensions")
10901012
and importlib.util.find_spec("werkzeug")
10911013
and not force_default_runserver
1092-
):
1093-
server_command = "runserver_plus"
1094-
runserver_options = get_runserver_options(
1095-
addrport=addrport,
1096-
use_ipv6=use_ipv6,
1097-
no_threading=no_threading,
1098-
no_static=no_static,
1099-
no_reloader=no_reloader,
1100-
skip_checks=skip_checks,
1101-
pdb=pdb,
1102-
ipdb=ipdb,
1103-
pm=pm,
1104-
print_sql=print_sql,
1105-
print_sql_location=print_sql_location,
1106-
cert_file=cert_file,
1107-
key_file=key_file,
1108-
)
1109-
else:
1110-
server_command = "runserver"
1111-
runserver_options = get_runserver_options(
1112-
addrport=addrport,
1113-
use_ipv6=use_ipv6,
1114-
no_threading=no_threading,
1115-
no_static=no_static,
1116-
no_reloader=no_reloader,
1117-
skip_checks=skip_checks,
1118-
)
1014+
)
1015+
server_command = "runserver_plus" if use_plus else "runserver"
11191016

1120-
# Prepare commands for concurrent execution
11211017
watch_cmd = [sys.executable, "manage.py", "tailwind", "watch"]
1122-
debug_server_cmd = [sys.executable, "manage.py", server_command] + runserver_options
1018+
server_cmd = [sys.executable, "manage.py", server_command, *ctx.args]
11231019

1124-
# Use improved process manager
11251020
process_manager = ProcessManager()
1126-
process_manager.start_concurrent_processes(watch_cmd, debug_server_cmd)
1021+
process_manager.start_concurrent_processes(watch_cmd, server_cmd)
11271022

11281023

11291024
# PROCESS MANAGEMENT FUNCTIONS -------------------------------------------------------------------
@@ -1714,51 +1609,3 @@ def _create_standard_config_with_verbose(*, verbose: bool = False) -> None:
17141609
)
17151610
elif verbose:
17161611
typer.secho("⏭️ Source CSS file is up-to-date, no changes needed", fg=typer.colors.GREEN)
1717-
1718-
1719-
def get_runserver_options(
1720-
*,
1721-
addrport: str | None = None,
1722-
use_ipv6: bool = False,
1723-
no_threading: bool = False,
1724-
no_static: bool = False,
1725-
no_reloader: bool = False,
1726-
skip_checks: bool = False,
1727-
pdb: bool = False,
1728-
ipdb: bool = False,
1729-
pm: bool = False,
1730-
print_sql: bool = False,
1731-
print_sql_location: bool = False,
1732-
cert_file: str | None = None,
1733-
key_file: str | None = None,
1734-
) -> list[str]:
1735-
options: list[str] = []
1736-
1737-
if use_ipv6:
1738-
options.append("--ipv6")
1739-
if no_threading:
1740-
options.append("--nothreading")
1741-
if no_static:
1742-
options.append("--nostatic")
1743-
if no_reloader:
1744-
options.append("--noreload")
1745-
if skip_checks:
1746-
options.append("--skip-checks")
1747-
if pdb:
1748-
options.append("--pdb")
1749-
if ipdb:
1750-
options.append("--ipdb")
1751-
if pm:
1752-
options.append("--pm")
1753-
if print_sql:
1754-
options.append("--print-sql")
1755-
if print_sql_location:
1756-
options.append("--print-sql-location")
1757-
if cert_file:
1758-
options.append(f"--cert-file={cert_file}")
1759-
if key_file:
1760-
options.append(f"--key-file={key_file}")
1761-
if addrport:
1762-
options.append(addrport)
1763-
1764-
return options

0 commit comments

Comments
 (0)