diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 4e94099..cf97772 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -16,11 +16,10 @@ jobs: python-version: 3.12 - name: Install build dependencies - run: python -m pip install build wheel + run: python -m pip install build - name: Build distributions - shell: bash -l {0} - run: python setup.py sdist bdist_wheel + run: python -m build - name: Publish package to PyPI if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags') diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..6313a0b --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,79 @@ +# Next Commerce Theme Kit + +CLI tool (`ntk`) for building and maintaining storefront themes on the Next Commerce platform. Supports Sass processing via `libsass`. + +## Project Structure + +``` +ntk/ + __main__.py # Entry point + command.py # All CLI commands (watch, push, pull, checkout, init, list, sass) + conf.py # Config loading and constants + decorator.py # @parser_config decorator for command validation + gateway.py # API client for Next Commerce store + utils.py # Helpers (get_template_name, progress_bar) +tests/ + test_command.py + test_gateway.py + test_config.py + test_installer.py +``` + +## Development Setup + +### Prerequisites + +- Python 3.10 or higher — check with `python --version` +- `pip` — usually included with Python +- On macOS, Python can be installed via [Homebrew](https://brew.sh): `brew install python` +- On Windows, use [WSL](https://docs.microsoft.com/en-us/windows/wsl/install) (recommended) or the [Windows App Store](https://apps.microsoft.com/store/detail/python-310/9PJPW5LDXLZ5) + +### First-time Setup + +```bash +# Clone the repo +git clone https://github.com/29next/theme-kit.git +cd theme-kit + +# Create and activate a virtual environment +python -m venv venv +source venv/bin/activate # macOS/Linux +# venv\Scripts\activate # Windows + +# Install the package with test dependencies +pip install -e ".[test]" +``` + +### Installing the CLI Globally (for end users) + +```bash +pip install next-theme-kit + +# Or with pipx (recommended — keeps it isolated) +pipx install next-theme-kit +``` + +## Running Tests + +```bash +pytest tests/ -v +pytest --cov=ntk --cov-report xml +``` + +## Key Dependencies + +- `watchfiles` — file watching for `ntk watch` (replaced deprecated `watchgod`) +- `libsass` — Sass/SCSS processing +- `PyYAML` — config.yml parsing +- `requests` — HTTP client for store API + +## Python Support + +Requires Python >= 3.10. Tested against 3.10, 3.11, 3.12, 3.13, 3.14 via tox and GitHub Actions. + +## Important Conventions + +- Use `asyncio.run()` for async entry points — not `get_event_loop()` (broke in Python 3.12+) +- `watchfiles.Change` is an `IntEnum` — use `event_type.name.title()` for human-readable log output, not `str(event_type)` +- `watchfiles` internal logging is suppressed via `logging.getLogger('watchfiles').setLevel(logging.WARNING)` +- Test dependencies are declared as `extras_require={"test": ...}` in `setup.py` diff --git a/README.md b/README.md index 4f077b9..3a0bd70 100644 --- a/README.md +++ b/README.md @@ -3,9 +3,9 @@ [![Build Status][GHAction-image]][GHAction-link] [![CodeCov][codecov-image]][codecov-link] -# 29 Next Theme Kit +# Next Commerce Theme Kit -Theme Kit is a cross-platform command line tool to build and maintain storefront themes with [Sass Processing](#sass-processing) support on the 29 Next platform. +Theme Kit is a cross-platform command line tool to build and maintain storefront themes with [Sass Processing](#sass-processing) support on the Next Commerce platform. ## Installation diff --git a/ntk/command.py b/ntk/command.py index a8025e5..be8203a 100644 --- a/ntk/command.py +++ b/ntk/command.py @@ -5,8 +5,7 @@ import time import sass -from watchgod import awatch -from watchgod.watcher import Change +from watchfiles import awatch, Change from ntk.conf import ( Config, MEDIA_FILE_EXTENSIONS, GLOB_PATTERN, SASS_DESTINATION, SASS_SOURCE @@ -21,6 +20,7 @@ level=logging.INFO, datefmt='%Y-%m-%d %H:%M:%S' ) +logging.getLogger('watchfiles').setLevel(logging.WARNING) class Command: @@ -46,10 +46,10 @@ def _handle_files_change(self, changes): for event_type, pathfile in changes: template_name = get_template_name(pathfile) if event_type in [Change.added, Change.modified]: - logging.info(f'[{self.config.env}] {str(event_type)} {template_name}') + logging.info(f'[{self.config.env}] {event_type.name.title()} {template_name}') self._push_templates([template_name], compile_sass=True) elif event_type == Change.deleted: - logging.info(f'[{self.config.env}] {str(event_type)} {template_name}') + logging.info(f'[{self.config.env}] {event_type.name.title()} {template_name}') self._delete_templates([template_name]) def _push_templates(self, template_names, compile_sass=False): @@ -198,8 +198,7 @@ async def main(): async for changes in awatch('.'): self._handle_files_change(changes) - loop = asyncio.get_event_loop() - loop.run_until_complete(main()) + asyncio.run(main()) @parser_config() def compile_sass(self, parser): diff --git a/setup.py b/setup.py index 71917b3..97aaf02 100644 --- a/setup.py +++ b/setup.py @@ -1,10 +1,11 @@ from setuptools import find_packages, setup -__version__ = '1.0.7' +__version__ = '1.1.0' tests_require = [ - "flake8==3.9.2", - "pytest==7.2.2" + "flake8", + "pytest", + "pytest-cov", ] with open('README.md', 'r') as fh: @@ -22,7 +23,7 @@ install_requires=[ "PyYAML>=5.4", "requests>=2.25", - "watchgod>=0.7", + "watchfiles>=0.18", "libsass>=0.21.0" ], entry_points={ @@ -30,6 +31,7 @@ 'ntk = ntk.__main__:main', ], }, + extras_require={"test": tests_require}, packages=find_packages(), - python_requires='>=3.8' + python_requires='>=3.10' ) diff --git a/tests/test_command.py b/tests/test_command.py index 4f63f4b..5f59d36 100644 --- a/tests/test_command.py +++ b/tests/test_command.py @@ -2,7 +2,7 @@ import unittest from unittest.mock import call, MagicMock, mock_open, patch -from watchgod.watcher import Change +from watchfiles import Change from ntk import conf from ntk.command import Command @@ -310,6 +310,62 @@ def test_pull_command_with_configs_and_filenames_should_be_download_only_file_in mock_write_config.assert_not_called() + ##### + # push + ##### + def test_push_command_without_config_file_should_be_required_api_key_store_and_theme_id(self): + with self.assertRaises(TypeError) as error: + self.parser.apikey = None + self.parser.store = None + self.parser.theme_id = None + self.command.push(self.parser) + self.assertEqual( + str(error.exception), '[development] argument -a/--apikey, -s/--store, -t/--theme_id are required.') + + @patch("ntk.command.Command._get_accept_files", autospec=True) + def test_push_command_with_configs_and_without_filenames_should_upload_all_files( + self, mock_get_accept_files + ): + mock_get_accept_files.return_value = [ + f'{os.getcwd()}/layout/base.html', + ] + self.mock_gateway.return_value.create_or_update_template.return_value.ok = True + self.mock_gateway.return_value.create_or_update_template.return_value.headers = { + 'content-type': 'application/json; charset=utf-8'} + self.command.config.parser_config(self.parser) + self.parser.filenames = None + with patch("builtins.open", self.mock_file): + self.command.push(self.parser) + expected_call = call().create_or_update_template( + theme_id=1234, + template_name='layout/base.html', + content='{% load i18n %}\n\n