From de00a04e7f9a1cfdc83ec77ae6aa09889235dc67 Mon Sep 17 00:00:00 2001 From: Alex Phelps Date: Tue, 25 Aug 2026 14:58:35 +0700 Subject: [PATCH 1/2] ignore checkout dir --- README.md | 2 +- ntk/command.py | 3 +++ ntk/conf.py | 1 - setup.py | 2 +- tests/test_command.py | 51 +++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 56 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 0719b5f..fe38e3d 100644 --- a/README.md +++ b/README.md @@ -155,7 +155,7 @@ On success, `ntk init` logs the new theme ID and name, and persists the theme ID To sync files between your local directory and the store, use `ntk push` to upload and `ntk pull` to download. Both upload or download the whole theme by default, and both accept file paths as positional arguments to limit the operation to specific files. > [!NOTE] -> File paths are relative to the theme root. `ntk push` only uploads files inside the theme directories (`assets`, `checkout`, `configs`, `layouts`, `locales`, `partials`, `sass`, `templates`) with valid theme file extensions — a path outside of them is skipped silently, not reported as an error. +> File paths are relative to the theme root. `ntk push` only uploads files inside the theme directories (`assets`, `configs`, `layouts`, `locales`, `partials`, `sass`, `templates`) with valid theme file extensions — a path outside of them is skipped silently, not reported as an error. The `checkout` directory is skipped by both `ntk pull` and `ntk push`: the store does not accept uploads to it. | Example | Command | | ------- | ------- | diff --git a/ntk/command.py b/ntk/command.py index 581e9ce..f975367 100644 --- a/ntk/command.py +++ b/ntk/command.py @@ -109,6 +109,9 @@ def _pull_templates(self, template_names): if not isinstance(templates, list): return + # The store API rejects uploads to checkout/ (#37), so never write it locally either. + templates = [template for template in templates if not str(template['name']).startswith('checkout/')] + template_count = len(templates) logging.info(f'[{self.config.env}] Connecting to {self.config.store}') logging.info(f'[{self.config.env}] Pulling {template_count} files from theme id {self.config.theme_id} ') diff --git a/ntk/conf.py b/ntk/conf.py index b6ff190..c13c41e 100644 --- a/ntk/conf.py +++ b/ntk/conf.py @@ -41,7 +41,6 @@ "assets/**/*.webm", "assets/**/*.mp3", "assets/**/*.pdf", - "checkout/**/*.html", "configs/**/*.json", "layouts/**/*.html", "partials/**/*.html", diff --git a/setup.py b/setup.py index 8acb5cd..dd45803 100644 --- a/setup.py +++ b/setup.py @@ -1,6 +1,6 @@ from setuptools import find_packages, setup -__version__ = '1.2.0' +__version__ = '1.3.0' tests_require = [ "flake8", diff --git a/tests/test_command.py b/tests/test_command.py index 13e1a14..09a0a20 100644 --- a/tests/test_command.py +++ b/tests/test_command.py @@ -1,4 +1,5 @@ import os +import tempfile import unittest from unittest.mock import call, MagicMock, mock_open, patch @@ -310,6 +311,41 @@ def test_pull_command_with_configs_and_filenames_should_be_download_only_file_in mock_write_config.assert_not_called() + @patch("ntk.command.os.makedirs", autospec=True) + @patch("builtins.open", autospec=True) + @patch("ntk.command.Config.write_config", autospec=True) + def test_pull_command_skips_checkout_directory(self, mock_write_config, mock_open_file, mock_makedirs): + """The store API rejects uploads to checkout/ (#37), so pull never writes it locally.""" + self.mock_gateway.return_value.get_templates.return_value.json.return_value = [ + { + "theme": 1234, + "name": "checkout/checkout.html", + "content": "{% extends 'checkout/base.html' %}", + "file": None + }, + { + "theme": 1234, + "name": "layout/base.html", + "content": "{% load i18n %}\n\n
My home page
", + "file": None + } + ] + + self.parser.filenames = None + with self.assertLogs(level='INFO') as logs: + self.command.pull(self.parser) + + self.assertIn('[development] Pulling 1 files from theme id 1234', ''.join(logs.output)) + + # create layout/base.html + self.assertIn( + call(os.path.abspath('layout/base.html'), 'w', encoding='utf-8'), mock_open_file.mock_calls) + + # never create checkout/ or checkout/checkout.html + self.assertNotIn(call(os.path.abspath('checkout')), mock_makedirs.mock_calls) + self.assertNotIn( + call(os.path.abspath('checkout/checkout.html'), 'w', encoding='utf-8'), mock_open_file.mock_calls) + ##### # push ##### @@ -410,6 +446,21 @@ def test_get_accept_files_filters_invalid_extensions_from_provided_filenames( ]) self.assertEqual(result, [valid_file]) + def test_get_accept_files_skips_checkout_directory(self): + """The store API rejects uploads to checkout/ (#37), so push never selects it.""" + cwd = os.getcwd() + with tempfile.TemporaryDirectory() as theme_dir: + os.chdir(theme_dir) + try: + os.makedirs('checkout') + os.makedirs('templates') + open('checkout/checkout.html', 'w').close() + open('templates/index.html', 'w').close() + self.assertEqual(self.command._get_accept_files([]), [os.path.abspath('templates/index.html')]) + self.assertEqual(self.command._get_accept_files(['checkout/checkout.html']), []) + finally: + os.chdir(cwd) + @patch("ntk.command.Command._get_accept_files", autospec=True) def test_push_command_with_filenames_should_upload_only_specified_files( self, mock_get_accept_files From 5f689934b1f9c30d78f3e521b5c1ed479645ff79 Mon Sep 17 00:00:00 2001 From: Alex Phelps Date: Tue, 25 Aug 2026 15:08:01 +0700 Subject: [PATCH 2/2] simplify --- README.md | 2 +- ntk/command.py | 3 --- tests/test_command.py | 35 ----------------------------------- 3 files changed, 1 insertion(+), 39 deletions(-) diff --git a/README.md b/README.md index fe38e3d..c776d31 100644 --- a/README.md +++ b/README.md @@ -155,7 +155,7 @@ On success, `ntk init` logs the new theme ID and name, and persists the theme ID To sync files between your local directory and the store, use `ntk push` to upload and `ntk pull` to download. Both upload or download the whole theme by default, and both accept file paths as positional arguments to limit the operation to specific files. > [!NOTE] -> File paths are relative to the theme root. `ntk push` only uploads files inside the theme directories (`assets`, `configs`, `layouts`, `locales`, `partials`, `sass`, `templates`) with valid theme file extensions — a path outside of them is skipped silently, not reported as an error. The `checkout` directory is skipped by both `ntk pull` and `ntk push`: the store does not accept uploads to it. +> File paths are relative to the theme root. `ntk push` only uploads files inside the theme directories (`assets`, `configs`, `layouts`, `locales`, `partials`, `sass`, `templates`) with valid theme file extensions — a path outside of them is skipped silently, not reported as an error. `ntk pull` still downloads the `checkout` directory, but `ntk push` skips it: the store does not accept uploads to it. | Example | Command | | ------- | ------- | diff --git a/ntk/command.py b/ntk/command.py index f975367..581e9ce 100644 --- a/ntk/command.py +++ b/ntk/command.py @@ -109,9 +109,6 @@ def _pull_templates(self, template_names): if not isinstance(templates, list): return - # The store API rejects uploads to checkout/ (#37), so never write it locally either. - templates = [template for template in templates if not str(template['name']).startswith('checkout/')] - template_count = len(templates) logging.info(f'[{self.config.env}] Connecting to {self.config.store}') logging.info(f'[{self.config.env}] Pulling {template_count} files from theme id {self.config.theme_id} ') diff --git a/tests/test_command.py b/tests/test_command.py index 09a0a20..2c18a7a 100644 --- a/tests/test_command.py +++ b/tests/test_command.py @@ -311,41 +311,6 @@ def test_pull_command_with_configs_and_filenames_should_be_download_only_file_in mock_write_config.assert_not_called() - @patch("ntk.command.os.makedirs", autospec=True) - @patch("builtins.open", autospec=True) - @patch("ntk.command.Config.write_config", autospec=True) - def test_pull_command_skips_checkout_directory(self, mock_write_config, mock_open_file, mock_makedirs): - """The store API rejects uploads to checkout/ (#37), so pull never writes it locally.""" - self.mock_gateway.return_value.get_templates.return_value.json.return_value = [ - { - "theme": 1234, - "name": "checkout/checkout.html", - "content": "{% extends 'checkout/base.html' %}", - "file": None - }, - { - "theme": 1234, - "name": "layout/base.html", - "content": "{% load i18n %}\n\n
My home page
", - "file": None - } - ] - - self.parser.filenames = None - with self.assertLogs(level='INFO') as logs: - self.command.pull(self.parser) - - self.assertIn('[development] Pulling 1 files from theme id 1234', ''.join(logs.output)) - - # create layout/base.html - self.assertIn( - call(os.path.abspath('layout/base.html'), 'w', encoding='utf-8'), mock_open_file.mock_calls) - - # never create checkout/ or checkout/checkout.html - self.assertNotIn(call(os.path.abspath('checkout')), mock_makedirs.mock_calls) - self.assertNotIn( - call(os.path.abspath('checkout/checkout.html'), 'w', encoding='utf-8'), mock_open_file.mock_calls) - ##### # push #####