From 2324d0ff59af2ed13400ba9525b5aecf475da2b9 Mon Sep 17 00:00:00 2001 From: game Date: Mon, 19 Jan 2026 15:48:00 +0700 Subject: [PATCH 01/10] update build file retrieval logic in command and utils and add get_lastest_build_file --- nak/command.py | 7 +++---- nak/utils.py | 18 +++++++++++++++--- 2 files changed, 18 insertions(+), 7 deletions(-) diff --git a/nak/command.py b/nak/command.py index 381be97..bf29f91 100644 --- a/nak/command.py +++ b/nak/command.py @@ -6,7 +6,7 @@ from nak.gateway import Gateway from nak.settings import (CONFIG_FILE, ENV_FILE, LOG_COLOR, ZIP_DESTINATION_DIRECTORY, ZIP_DESTINATION_PATH) -from nak.utils import (get_all_file, get_error_from_response, hide_variable, +from nak.utils import (get_all_file, get_error_from_response, hide_variable, get_lastest_build_file, progress_bar) logging.basicConfig( @@ -76,11 +76,10 @@ def push(self, parser=None): raise TypeError(LOG_COLOR.ERROR.format( message=('No build file available. Run "nak build".'))) - zip_file_list = get_all_file(path=f"./{ZIP_DESTINATION_DIRECTORY}", required_extension='.zip') - if not zip_file_list: + latest_build_file = get_lastest_build_file() + if not latest_build_file: raise TypeError(LOG_COLOR.ERROR.format(message='Please run build before push command.')) - latest_build_file = sorted(zip_file_list, key=lambda file_name: file_name, reverse=True)[0] file_name = latest_build_file.split("/")[-1] logging.info(LOG_COLOR.INFO.format(message=f'Pushing to app with client_id {self.config.client_id}')) diff --git a/nak/utils.py b/nak/utils.py index 4adfcab..27a0ea6 100644 --- a/nak/utils.py +++ b/nak/utils.py @@ -2,11 +2,23 @@ import time from pathlib import Path -from nak.settings import ALLOW_FILE_EXTENSIONS, ZIP_EXCLUDE_FILES +from nak.settings import ALLOW_FILE_EXTENSIONS, ZIP_DESTINATION_DIRECTORY, ZIP_EXCLUDE_FILES -def get_latest_zip(pathfile): - return Path(os.path.relpath(pathfile)).as_posix() +def get_lastest_build_file(): + path = Path(f"./{ZIP_DESTINATION_DIRECTORY}") + + zip_files = [f for f in path.iterdir() if f.is_file() and f.suffix == ".zip"] + if not zip_files: + return None + + zip_files_sorted = sorted( + zip_files, + key=lambda f: f.stat().st_ctime, + reverse=True + ) + + return zip_files_sorted[0].as_posix() def progress_bar(iterable, prefix='', suffix='', decimals=1, length=100, fill='█', printEnd="\r"): From c302a891ad816ee993b56ef726ab82ec5fc5a733 Mon Sep 17 00:00:00 2001 From: game Date: Mon, 19 Jan 2026 15:48:10 +0700 Subject: [PATCH 02/10] update test --- tests/test_command.py | 19 +++++++++---------- tests/test_utils.py | 38 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 47 insertions(+), 10 deletions(-) diff --git a/tests/test_command.py b/tests/test_command.py index 3e63224..636e26c 100644 --- a/tests/test_command.py +++ b/tests/test_command.py @@ -116,12 +116,10 @@ def test_build_with_correct_directory_should_create_zip_file_correctly( # push ##### @patch("os.path.exists", autospec=True) - @patch("nak.command.get_all_file", autospec=True) @patch("nak.command.Config.write_config", autospec=True) def test_push_with_wrong_directory_should_raise_error_correctly( - self, mock_write_config, mock_get_file, mock_path_exists + self, mock_write_config, mock_path_exists ): - mock_get_file.return_value = ["test1.file", "test2.file"] mock_path_exists.side_effect = [ # check envfile at read_config False, @@ -139,12 +137,13 @@ def test_push_with_wrong_directory_should_raise_error_correctly( @patch("builtins.open", autospec=True) @patch("os.path.exists", autospec=True) - @patch("nak.command.get_all_file", autospec=True) + @patch("nak.command.get_lastest_build_file", autospec=True) @patch("nak.command.Config.write_config", autospec=True) def test_push_with_failed_on_server_should_log_error_correctly( self, mock_write_config, mock_get_file, mock_path_exists, mock_open_file ): - mock_get_file.return_value = ["test1-20200101010101.zip", "test2-20200101010102.zip"] + mock_get_file.return_value = "test1-20200101010101.zip" + mock_path_exists.side_effect = [ # check envfile at read_config True, @@ -165,24 +164,24 @@ def test_push_with_failed_on_server_should_log_error_correctly( self.mock_gateway.return_value.update_app.assert_called_once_with( files={ # send with latest file - 'file': ('test2-20200101010102.zip', file) + 'file': ('test1-20200101010101.zip', file) } ) assert log.output == [ f'INFO:root:{LOG_COLOR.INFO.format(message="Pushing to app with client_id 123456")}', - f'INFO:root:{LOG_COLOR.INFO.format(message="with filename test2-20200101010102.zip")}', + f'INFO:root:{LOG_COLOR.INFO.format(message="with filename test1-20200101010101.zip")}', f'INFO:root:{LOG_COLOR.INFO.format(message="by username test@29next.com")}', f"INFO:root:{LOG_COLOR.ERROR.format(message='Upload file to server failed. file size limit')}" ] @patch("builtins.open", autospec=True) @patch("os.path.exists", autospec=True) - @patch("nak.command.get_all_file", autospec=True) + @patch("nak.command.get_lastest_build_file", autospec=True) @patch("nak.command.Config.write_config", autospec=True) def test_push_with_correct_directory_should_call_gateway_correctly( self, mock_write_config, mock_get_file, mock_path_exists, mock_open_file ): - mock_get_file.return_value = ["test1-20200101010101.zip", "test2-20200101010102.zip"] + mock_get_file.return_value = "test2-20200101010102.zip" mock_path_exists.side_effect = [ # check envfile at read_config True, @@ -233,7 +232,7 @@ def test_push_with_no_directory_should_raise_error( message=('No build file available. Run "nak build".')) @patch("os.path.exists", autospec=True) - @patch("nak.command.get_all_file", autospec=True) + @patch("nak.command.get_lastest_build_file", autospec=True) @patch("nak.command.Config.write_config", autospec=True) def test_push_with_no_files_in_directory_should_raise_error( self, mock_write_config, mock_get_file, mock_path_exists diff --git a/tests/test_utils.py b/tests/test_utils.py index 5bc3d4a..047470c 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -67,3 +67,41 @@ def test_get_all_file_with_required_extension_file_should_return_correctly_file_ actual = utils.get_all_file('.', required_extension='.zip') assert actual == ['./test_zip1.zip', './test_zip2.zip'] + + #### + # get_lastest_build_file + ### + @patch("nak.utils.Path") + def test_get_lastest_build_file_with_no_zip_file_should_return_none(self, mock_path): + mock_dir = MagicMock() + mock_path.return_value = mock_dir + + mock_dir.iterdir.return_value = [ + MagicMock( + is_file=MagicMock(return_value=True), suffix='.txt', as_posix=MagicMock(return_value='file1.txt')), + MagicMock( + is_file=MagicMock(return_value=True), suffix='.txt', as_posix=MagicMock(return_value='file2.txt')) + ] + + result = utils.get_lastest_build_file() + assert result is None + + @patch("nak.utils.Path") + def test_get_lastest_build_file_with_have_zip_fiile_should_return_latest_file(self, mock_path): + mock_dir = MagicMock() + mock_path.return_value = mock_dir + + mock_file1 = MagicMock( + is_file=MagicMock(return_value=True), suffix='.zip', as_posix=MagicMock(return_value='file1.txt')) + mock_file1.stat.return_value.st_ctime = 1000 + mock_file1.as_posix.return_value = 'file1.zip' + + mock_file2 = MagicMock( + is_file=MagicMock(return_value=True), suffix='.zip', as_posix=MagicMock(return_value='file2.txt')) + mock_file2.stat.return_value.st_ctime = 2000 + mock_file2.as_posix.return_value = 'file2.zip' + + mock_dir.iterdir.return_value = [mock_file1, mock_file2] + + result = utils.get_lastest_build_file() + assert result == 'file2.zip' From 67254fce5b13791bbe92196b858dd6f28e0501f5 Mon Sep 17 00:00:00 2001 From: game Date: Mon, 19 Jan 2026 15:51:01 +0700 Subject: [PATCH 03/10] upgrade version to 1.0.4 --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 2494a2d..f66ae10 100644 --- a/setup.py +++ b/setup.py @@ -1,6 +1,6 @@ from setuptools import find_packages, setup -__version__ = '1.0.3' +__version__ = '1.0.4' tests_require = [ "flake8==3.9.2", From 4496907a770562787d3c0c52bd8adeabfa07cdaf Mon Sep 17 00:00:00 2001 From: game Date: Mon, 19 Jan 2026 16:00:14 +0700 Subject: [PATCH 04/10] fix flake8 --- nak/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nak/utils.py b/nak/utils.py index 27a0ea6..6beb2b1 100644 --- a/nak/utils.py +++ b/nak/utils.py @@ -84,7 +84,7 @@ def get_error_from_response(response): result = response.json() error_msg = "" for key, value in result.items(): - if type(value) == list: + if isinstance(value, list): error_msg += f'"{key}" : {" ".join(value)}' else: error_msg += value From 2ffdb4e42689789c9473ef1dfef04ce4b28c05fc Mon Sep 17 00:00:00 2001 From: game Date: Mon, 19 Jan 2026 16:04:20 +0700 Subject: [PATCH 05/10] update python test version --- .github/workflows/test.yml | 2 +- tox.ini | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 4fca68c..074efff 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -23,7 +23,7 @@ jobs: strategy: fail-fast: true matrix: - python-version: [3.6, 3.7, 3.8, 3.9] + python-version: ['3.8', '3.9', '3.10', '3.11', '3.12'] steps: - name: Check out repository code uses: actions/checkout@v2 diff --git a/tox.ini b/tox.ini index 0992132..72e8967 100644 --- a/tox.ini +++ b/tox.ini @@ -1,13 +1,13 @@ [tox] -envlist = py36, py37, py38, py39 +envlist = py37, py38, py39, py310 skip_missing_interpreters = true [gh-actions] python = - 3.6: py36 3.7: py37 3.8: py38 3.9: py39 + 3.10: py310 [testenv] allowlist_externals = /usr/bin/test From 66d92efe02dc644890268f3879b4e6e9c551fa14 Mon Sep 17 00:00:00 2001 From: game Date: Mon, 19 Jan 2026 16:13:38 +0700 Subject: [PATCH 06/10] remove unsupported Python versions from CI and update testing dependencies --- .github/workflows/test.yml | 2 +- setup.py | 2 +- tox.ini | 8 ++++---- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 074efff..2851eed 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -23,7 +23,7 @@ jobs: strategy: fail-fast: true matrix: - python-version: ['3.8', '3.9', '3.10', '3.11', '3.12'] + python-version: ['3.8', '3.9', '3.10'] steps: - name: Check out repository code uses: actions/checkout@v2 diff --git a/setup.py b/setup.py index f66ae10..8ebbd59 100644 --- a/setup.py +++ b/setup.py @@ -4,7 +4,7 @@ tests_require = [ "flake8==3.9.2", - "nose==1.3.7" + "pytest==7.2.2" ] with open('README.md', 'r') as fh: diff --git a/tox.ini b/tox.ini index 72e8967..47655b9 100644 --- a/tox.ini +++ b/tox.ini @@ -12,9 +12,9 @@ python = [testenv] allowlist_externals = /usr/bin/test deps = - coverage - codecov + pytest + pytest-cov flake8 - nose + codecov -commands = nosetests -v --with-coverage --cover-inclusive --cover-xml --cover-package=nak +commands = pytest --cov=nak --cov-report xml From 31da0b75f1acc7478b5eab0072646f029ff3ca6e Mon Sep 17 00:00:00 2001 From: game Date: Mon, 19 Jan 2026 16:15:45 +0700 Subject: [PATCH 07/10] update Codecov action to v5 and ensure CODECOV_TOKEN is set in the environment --- .github/workflows/test.yml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 2851eed..2640927 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -40,6 +40,9 @@ jobs: run: tox - name: "Upload coverage to Codecov" - uses: codecov/codecov-action@v1 + uses: codecov/codecov-action@v5 with: fail_ci_if_error: true + env: + CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} + From 616da77c8bb246d07b2abbfc59814ae756c139a8 Mon Sep 17 00:00:00 2001 From: game Date: Tue, 20 Jan 2026 10:01:25 +0700 Subject: [PATCH 08/10] add logging for created build file in Command class --- nak/command.py | 1 + tests/test_command.py | 5 ++++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/nak/command.py b/nak/command.py index bf29f91..6dade25 100644 --- a/nak/command.py +++ b/nak/command.py @@ -63,6 +63,7 @@ def build(self, parser=None): new_zip.write(file) new_zip.close() + logging.info(LOG_COLOR.INFO.format(message=f'Created build file with {destination_file.split("/")[-1]}.')) logging.info(LOG_COLOR.SUCCESS.format(message='Build successfully.')) def push(self, parser=None): diff --git a/tests/test_command.py b/tests/test_command.py index 636e26c..c476115 100644 --- a/tests/test_command.py +++ b/tests/test_command.py @@ -2,7 +2,7 @@ from unittest.mock import MagicMock, call, mock_open, patch from nak.command import Command -from nak.settings import LOG_COLOR +from nak.settings import LOG_COLOR, ZIP_DESTINATION_PATH class TestCommand(unittest.TestCase): @@ -106,9 +106,12 @@ def test_build_with_correct_directory_should_create_zip_file_correctly( call.write('test2.file'), call.close(), ] + + file_name = ZIP_DESTINATION_PATH.format(app_name="app-kit").split('/')[-1] assert log.output == [ f"INFO:root:{LOG_COLOR.INFO.format(message='file: test1.file')}", f"INFO:root:{LOG_COLOR.INFO.format(message='file: test2.file')}", + f"INFO:root:{LOG_COLOR.INFO.format(message=f'Created build file with {file_name}.')}", f"INFO:root:{LOG_COLOR.SUCCESS.format(message='Build successfully.')}" ] From cfb08aafe0ef2951214595ec22d80867df3d3f31 Mon Sep 17 00:00:00 2001 From: game Date: Tue, 20 Jan 2026 10:38:19 +0700 Subject: [PATCH 09/10] update python version release to use 3.9 --- .github/workflows/release.yml | 4 ++-- nak/utils.py | 9 ++------- 2 files changed, 4 insertions(+), 9 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 3f63841..769d25e 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -10,10 +10,10 @@ jobs: - name: Checkout source uses: actions/checkout@v2 - - name: Set up Python 3.8 + - name: Set up Python 3.9 uses: actions/setup-python@v1 with: - python-version: 3.8 + python-version: 3.9 - name: Install build dependencies run: python -m pip install build wheel diff --git a/nak/utils.py b/nak/utils.py index 6beb2b1..7dc279f 100644 --- a/nak/utils.py +++ b/nak/utils.py @@ -12,13 +12,8 @@ def get_lastest_build_file(): if not zip_files: return None - zip_files_sorted = sorted( - zip_files, - key=lambda f: f.stat().st_ctime, - reverse=True - ) - - return zip_files_sorted[0].as_posix() + latest_zip = max(zip_files, key=lambda f: f.stat().st_ctime) + return latest_zip.as_posix() def progress_bar(iterable, prefix='', suffix='', decimals=1, length=100, fill='█', printEnd="\r"): From 1532942597dc783341e1e12bf1f675573359dc84 Mon Sep 17 00:00:00 2001 From: game Date: Tue, 20 Jan 2026 10:40:03 +0700 Subject: [PATCH 10/10] update python version release to use 3.10.19 --- .github/workflows/release.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 769d25e..3fb3372 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -10,10 +10,10 @@ jobs: - name: Checkout source uses: actions/checkout@v2 - - name: Set up Python 3.9 + - name: Set up Python 3.10.19 uses: actions/setup-python@v1 with: - python-version: 3.9 + python-version: 3.10.19 - name: Install build dependencies run: python -m pip install build wheel