From 23146890a8bc9cb6ace1ca045402bfc728e01b9b Mon Sep 17 00:00:00 2001 From: Oliver Andrich Date: Sat, 7 Dec 2024 19:17:59 +0100 Subject: [PATCH] Add PyCharm workaround commands for Tailwind CLI Introduce `install_pycharm_workaround` and `uninstall_pycharm_workaround` management commands to facilitate the integration of Tailwind CLI with PyCharm. --- CHANGELOG.md | 1 + README.md | 11 ++- docs/installation.md | 8 ++ docs/usage.md | 22 +++++ .../management/commands/tailwind.py | 73 ++++++++++++++++- tests/test_management_commands.py | 80 +++++++++++++++++++ uv.lock | 2 +- 7 files changed, 191 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6d5c295..4c8f8a6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ ## 2.21.0 +- Added `install_pycharm_workaround` and `remove_pycharm_workaround` management commands. [#142](https://github.com/django-commons/django-tailwind-cli/issues/142) - Added `remove_cli` subcommand to remove the CLI. [#132](https://github.com/django-commons/django-tailwind-cli/issues/132) - Refactored all the class based management command into simpler function based commands. - Refactored the process to build the runserver command. diff --git a/README.md b/README.md index 4bb731c..6d46f13 100644 --- a/README.md +++ b/README.md @@ -63,13 +63,16 @@ Checkout the detailed [installation guide](https://django-tailwind-cli.rtfd.io/l - Simplest possible integration. - Management commands: - - To start the Tailwind CLI in watch mode during development. - - To build the production grade CSS file for your project. - - To start a debug server along with the Tailwind CLI in watch mode in a single session. -- Necessary configuration to adapt the library to your project, when the defaults don't fit you. + + * To start the Tailwind CLI in watch mode during development. + * To build the production grade CSS file for your project. + * To start a debug server along with the Tailwind CLI in watch mode in a single session. + +- Configuration options to adapt the library to your project, when the defaults don't fit you. - A template tag to include the Tailwind CSS file in your project. - A base template for your project. - A sane tailwind.config.js that activates all the official plugins and includes a simple HTMX plugin. +- A management command to install [a workaround to support Tailwind CLI in PyCharm](https://django-tailwind-cli.rtfd.io/usage/#use-with-pycharm). ## Requirements diff --git a/docs/installation.md b/docs/installation.md index 2a563fb..8e34b60 100644 --- a/docs/installation.md +++ b/docs/installation.md @@ -78,6 +78,14 @@ hide: ## Optional steps +### Install PyCharm workaround + +In order [to use Tailwind CLI with PyCharm](/usage/#use-with-pycharm) you have to install a workaround. + +```shell +python manage.py tailwind install_pycharm_workaround +``` + ### Install `django-browser-reload` If you enjoy automatic reloading during development. Install the [django-browser-reload](https://github.com/adamchainz/django-browser-reload) app. The following installation steps are taken from the README of the project. diff --git a/docs/usage.md b/docs/usage.md index ad991d2..c648d16 100644 --- a/docs/usage.md +++ b/docs/usage.md @@ -92,6 +92,28 @@ Options: Run `python manage.py tailwind watch` to just start a tailwind watcher process if you prefer to start your debug server in a seperate shell or prefer a different solution than runserver or runserver_plus. +## Use with PyCharm + +[PyCharm](https://www.jetbrains.com/pycharm/) is a great IDE, but its Tailwind CSS plugin doesn't work well with the standalone CLI this package uses. Luckily, there is a [workaround](https://youtrack.jetbrains.com/issue/WEB-55647/Support-Tailwind-css-autocompletion-using-standalone-tailwind-CLI#focus=Comments-27-10957961.0-0) for this. + +### install_pycharm_workaround + +Run `python manage.py tailwind install_pycharm_workaround` to install the workaround in your project. + +!!! info "Update .gitignore file" + + + Update your `.gitignore` manually so that it exludes the created `package.json` file and the `node_modules` directory, that is created by this management command. + + ```gitignore + package.json + node_modules/ + ``` + +### remove_pycharm_workaround + +Run `python manage.py tailwind install_pycharm_workaround` to remove the workaround from your project. + ## Use with Docker Compose When used in the `watch` mode, the Tailwind CLI requires a TTY-enabled environment to function correctly. In a Docker Compose setup, ensure that the container executing the Tailwind style rebuild command (either `python manage.py tailwind runserver` or `python manage.py tailwind watch`, as noted above) is configured with the `tty: true` setting in your `docker-compose.yml`. diff --git a/src/django_tailwind_cli/management/commands/tailwind.py b/src/django_tailwind_cli/management/commands/tailwind.py index 8da2949..fec52b0 100644 --- a/src/django_tailwind_cli/management/commands/tailwind.py +++ b/src/django_tailwind_cli/management/commands/tailwind.py @@ -2,6 +2,7 @@ import importlib.util import os +import shutil import subprocess import sys from multiprocessing import Process @@ -242,7 +243,77 @@ def runserver_plus( ) -# UTILITY FUNCTIONS -------------------------------------------------------------------------------- +@app.command(name="install_pycharm_workaround") +def install_pycharm_workaround(): + """ + Configures the workarounds for PyCharm to get tailwind plugin to work with the tailwind CLI. + """ + _validate_config() + _download_cli() + + package_json = settings.BASE_DIR / "package.json" + package_json_content = '{"devDependencies": {"tailwindcss": "latest"}}' + cli_js = settings.BASE_DIR / "node_modules" / "tailwindcss" / "lib" / "cli.js" + + if package_json.exists(): + if package_json.read_text() == package_json_content: + typer.secho( + f"PyCharm workaround is already installed at '{package_json}'.", + fg=typer.colors.GREEN, + ) + return + else: + typer.secho( + f"Found an existing package.json at '{package_json}' that is " "not compatible.", + fg=typer.colors.YELLOW, + ) + return + else: + package_json.write_text(package_json_content) + typer.secho( + f"Created package.json at '{package_json}'", + fg=typer.colors.GREEN, + ) + + cli_js.parent.mkdir(parents=True, exist_ok=True) + cli_path = utils.get_full_cli_path() + cli_js.symlink_to(cli_path) + typer.secho( + f"Created link at '{cli_js}' to '{cli_path}'.", + fg=typer.colors.GREEN, + ) + typer.secho( + "\nAssure that you have added package.json and node_modules to your .gitignore file.", + fg=typer.colors.YELLOW, + ) + + +@app.command(name="uninstall_pycharm_workaround") +def uninstall_pycharm_workaround(): + package_json = settings.BASE_DIR / "package.json" + package_json_content = '{"devDependencies": {"tailwindcss": "latest"}}' + node_modules = settings.BASE_DIR / "node_modules" + + if package_json.exists() and package_json.read_text() == package_json_content: + package_json.unlink() + shutil.rmtree(node_modules) + typer.secho( + "Removed package.json and cli.js.", + fg=typer.colors.GREEN, + ) + elif package_json.exists() and package_json.read_text() != package_json_content: + typer.secho( + f"Found an existing package.json at '{package_json}' was not installed by us.", + fg=typer.colors.YELLOW, + ) + else: + typer.secho( + "No package.json or cli.js found.", + fg=typer.colors.YELLOW, + ) + + +# UTILITY FUNCTIONS ------------------------------------------------------------------------------- def _validate_config(): diff --git a/tests/test_management_commands.py b/tests/test_management_commands.py index 31c42b0..62efdaf 100644 --- a/tests/test_management_commands.py +++ b/tests/test_management_commands.py @@ -314,3 +314,83 @@ def test_list_projecttest_list_project_all_templates_templates(capsys, settings) assert "templates/tailwind_cli/tailwind_css.html" in captured.out assert "templates/tests/base.html" in captured.out assert "templates/admin" in captured.out + + +def test_install_pycharm_workaround(settings, tmp_path, capsys): + settings.BASE_DIR = tmp_path + settings.TAILWIND_CLI_PATH = str(tmp_path) + package_json = settings.BASE_DIR / "package.json" + cli_js = settings.BASE_DIR / "node_modules" / "tailwindcss" / "lib" / "cli.js" + + assert not package_json.exists() + assert not cli_js.exists() + + call_command("tailwind", "install_pycharm_workaround") + + assert package_json.exists() + assert ( + settings.BASE_DIR / "package.json" + ).read_text() == '{"devDependencies": {"tailwindcss": "latest"}}' + captured = capsys.readouterr() + assert "Created package.json" in captured.out + + assert cli_js.exists() + assert cli_js.resolve() == utils.get_full_cli_path() + assert "Created link at" in captured.out + + assert ( + "Assure that you have added package.json and node_modules to your .gitignore file." + in captured.out + ) + + +def test_install_pycharm_workaround_twice(settings, tmp_path, capsys): + settings.BASE_DIR = tmp_path + settings.TAILWIND_CLI_PATH = str(tmp_path) + call_command("tailwind", "install_pycharm_workaround") + call_command("tailwind", "install_pycharm_workaround") + captured = capsys.readouterr() + assert "PyCharm workaround is already installed at" in captured.out + + +def test_install_pycharm_workaround_with_existing(settings, tmp_path, capsys): + settings.BASE_DIR = tmp_path + settings.TAILWIND_CLI_PATH = str(tmp_path) + + package_json = settings.BASE_DIR / "package.json" + package_json.write_text("{}") + + call_command("tailwind", "install_pycharm_workaround") + captured = capsys.readouterr() + assert "Found an existing package.json at" in captured.out + assert "that is not compatible." in captured.out + + +def test_uninstall_pycharm_workaround(settings, tmp_path, capsys): + settings.BASE_DIR = tmp_path + settings.TAILWIND_CLI_PATH = str(tmp_path) + call_command("tailwind", "install_pycharm_workaround") + call_command("tailwind", "uninstall_pycharm_workaround") + captured = capsys.readouterr() + assert "Removed package.json and cli.js." in captured.out + + +def test_uninstall_pycharm_workaround_with_other_package_json(settings, tmp_path, capsys): + settings.BASE_DIR = tmp_path + settings.TAILWIND_CLI_PATH = str(tmp_path) + + package_json = settings.BASE_DIR / "package.json" + package_json.write_text("{}") + + call_command("tailwind", "uninstall_pycharm_workaround") + captured = capsys.readouterr() + assert "Found an existing package.json at" in captured.out + assert "was not installed by us." in captured.out + + +def test_uninstall_pycharm_workaround_without_install(settings, tmp_path, capsys): + settings.BASE_DIR = tmp_path + settings.TAILWIND_CLI_PATH = str(tmp_path) + call_command("tailwind", "uninstall_pycharm_workaround") + captured = capsys.readouterr() + assert "No package.json or cli.js found." in captured.out diff --git a/uv.lock b/uv.lock index a87c051..412d4b6 100644 --- a/uv.lock +++ b/uv.lock @@ -291,7 +291,7 @@ wheels = [ [[package]] name = "django-tailwind-cli" -version = "2.20.3.dev9+g753f72a.d20241207" +version = "2.20.3.dev3+gc98c91f.d20241207" source = { editable = "." } dependencies = [ { name = "django" },