diff --git a/pyproject.toml b/pyproject.toml index 7c14ce54..77e76bbe 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -58,10 +58,8 @@ docs_generate = "sphinx-build -b markdown -E docs/sphinx docs/cli" docs_toc = "gfm-toc docs/cli/index.md -e 3" docs_title = {shell = "(echo \"# AlgoKit CLI Reference Documentation\\n\\n\"; cat docs/cli/index.md) > docs/cli/temp.md && mv docs/cli/temp.md docs/cli/index.md"} docs = ["docs_generate", "docs_toc", "docs_title"] -package_unix = "pyinstaller --clean --onedir --hidden-import jinja2_ansible_filters --hidden-import multiformats_config --copy-metadata algokit --name algokit --noconfirm src/algokit/__main__.py --add-data './misc/multiformats_config/multibase-table.json:multiformats_config/' --add-data './misc/multiformats_config/multicodec-table.json:multiformats_config/'" -package_windows = "pyinstaller --clean --onedir --hidden-import jinja2_ansible_filters --hidden-import multiformats_config --copy-metadata algokit --name algokit --noconfirm src/algokit/__main__.py --add-data ./misc/multiformats_config/multibase-table.json;multiformats_config/ --add-data ./misc/multiformats_config/multicodec-table.json;multiformats_config/" - - +package_unix = "pyinstaller --clean --onedir --hidden-import jinja2_ansible_filters --hidden-import multiformats_config --copy-metadata algokit --name algokit --noconfirm src/algokit/__main__.py --add-data './misc/multiformats_config:multiformats_config/' --add-data './src/algokit/resources:algokit/resources/'" +package_windows = "pyinstaller --clean --onedir --hidden-import jinja2_ansible_filters --hidden-import multiformats_config --copy-metadata algokit --name algokit --noconfirm src/algokit/__main__.py --add-data ./misc/multiformats_config;multiformats_config/ --add-data ./src/algokit/resources;algokit/resources/" [tool.ruff] line-length = 120 select = [ diff --git a/src/algokit/core/utils.py b/src/algokit/core/utils.py index 2d0a934f..084f2864 100644 --- a/src/algokit/core/utils.py +++ b/src/algokit/core/utils.py @@ -150,3 +150,11 @@ def get_base_python_path() -> str | None: return str(candidate_path) # give up, we tried... return this_python + + +def is_binary_mode() -> bool: + """ + Check if the current Python interpreter is running in a native binary frozen environment. + return: True if running in a native binary frozen environment, False otherwise. + """ + return getattr(sys, "frozen", False) and hasattr(sys, "_MEIPASS") diff --git a/src/algokit/core/version_prompt.py b/src/algokit/core/version_prompt.py index 0bc98d19..75838da4 100644 --- a/src/algokit/core/version_prompt.py +++ b/src/algokit/core/version_prompt.py @@ -1,3 +1,4 @@ +import importlib.resources as importlib_resources import logging import re from datetime import timedelta @@ -6,13 +7,23 @@ import click import httpx +from algokit import __name__ as algokit_name from algokit.core.conf import get_app_config_dir, get_app_state_dir, get_current_package_version +from algokit.core.utils import is_binary_mode logger = logging.getLogger(__name__) LATEST_URL = "https://api.github.com/repos/algorandfoundation/algokit-cli/releases/latest" VERSION_CHECK_INTERVAL = timedelta(weeks=1).total_seconds() DISABLE_CHECK_MARKER = "disable-version-prompt" +DISTRIBUTION_METHOD_UPDATE_COMMAND = { + "snap": "`snap refresh algokit`", + "winget": "`winget upgrade algokit`", + "brew": "`brew upgrade algokit`", +} +UNKNOWN_DISTRIBUTION_METHOD_UPDATE_INSTRUCTION = "the tool used to install AlgoKit" +# TODO: Set this version as part of releasing the binary distributions. +BINARY_DISTRIBUTION_RELEASE_VERSION = "99.99.99" def do_version_prompt() -> None: @@ -26,8 +37,26 @@ def do_version_prompt() -> None: logger.debug("Could not determine latest version") return - if _get_version_sequence(current_version) < _get_version_sequence(latest_version): - logger.info(f"You are using AlgoKit version {current_version}, however version {latest_version} is available.") + current_version_sequence = _get_version_sequence(current_version) + if current_version_sequence < _get_version_sequence(latest_version): + update_instruction = UNKNOWN_DISTRIBUTION_METHOD_UPDATE_INSTRUCTION + if is_binary_mode(): + distribution = _get_distribution_method() + update_instruction = ( + DISTRIBUTION_METHOD_UPDATE_COMMAND.get(distribution, UNKNOWN_DISTRIBUTION_METHOD_UPDATE_INSTRUCTION) + if distribution + else UNKNOWN_DISTRIBUTION_METHOD_UPDATE_INSTRUCTION + ) + # If you're not using the binary mode, then you've used pipx to install AlgoKit. + # One exception is that older versions of the brew package used pipx, + # however require updating via brew, so we show the default update instruction instead. + elif current_version_sequence >= _get_version_sequence(BINARY_DISTRIBUTION_RELEASE_VERSION): + update_instruction = "`pipx upgrade algokit`" + + logger.info( + f"You are using AlgoKit version {current_version}, however version {latest_version} is available. " + f"Please update using {update_instruction}." + ) else: logger.debug("Current version is up to date") @@ -85,6 +114,17 @@ def _skip_version_prompt() -> bool: return disable_marker.exists() +def _get_distribution_method() -> str | None: + file_path = importlib_resources.files(algokit_name) / "resources" / "distribution-method" + with file_path.open("r", encoding="utf-8", errors="strict") as file: + content = file.read().strip() + + if content in ["snap", "winget", "brew"]: + return content + else: + return None + + skip_version_check_option = click.option( "--skip-version-check", is_flag=True, diff --git a/src/algokit/resources/distribution-method b/src/algokit/resources/distribution-method new file mode 100644 index 00000000..e69de29b diff --git a/tests/version_check/test_version_check.py b/tests/version_check/test_version_check.py index df4d72ee..d33c0bcf 100644 --- a/tests/version_check/test_version_check.py +++ b/tests/version_check/test_version_check.py @@ -140,3 +140,26 @@ def test_version_check_enable_version_check(app_dir_mock: AppDirs) -> None: assert result.exit_code == 0 assert not disable_version_check_path.exists() verify(result.output, scrubber=make_scrubber(app_dir_mock)) + + +@pytest.mark.parametrize( + ("method", "message"), + [ + ("snap", "snap refresh algokit"), + ("brew", "brew upgrade algokit"), + ("winget", "winget upgrade algokit"), + (None, "the tool used to install AlgoKit"), + ], +) +def test_version_prompt_according_to_distribution_method( + mocker: MockerFixture, app_dir_mock: AppDirs, method: str, message: str +) -> None: + mocker.patch("algokit.core.version_prompt._get_distribution_method").return_value = method + mocker.patch("algokit.core.version_prompt.is_binary_mode").return_value = True + mocker.patch("algokit.core.version_prompt.get_current_package_version").return_value = "1.0.0" + version_cache = app_dir_mock.app_state_dir / "last-version-check" + version_cache.write_text("2.0.0", encoding="utf-8") + + result = invoke("bootstrap env", skip_version_check=False) + assert result.exit_code == 0 + assert message in result.output diff --git a/tests/version_check/test_version_check.test_version_check_queries_github_when_cache_out_of_date.approved.txt b/tests/version_check/test_version_check.test_version_check_queries_github_when_cache_out_of_date.approved.txt index 59d9cebe..9760a037 100644 --- a/tests/version_check/test_version_check.test_version_check_queries_github_when_cache_out_of_date.approved.txt +++ b/tests/version_check/test_version_check.test_version_check_queries_github_when_cache_out_of_date.approved.txt @@ -1,6 +1,6 @@ DEBUG: 1234.56.78 found in cache {app_state}/last-version-check DEBUG: HTTP Request: GET https://api.github.com/repos/algorandfoundation/algokit-cli/releases/latest "HTTP/1.1 200 OK" DEBUG: Latest version tag: v{new_version} -You are using AlgoKit version {current_version}, however version {new_version} is available. +You are using AlgoKit version {current_version}, however version {new_version} is available. Please update using the tool used to install AlgoKit. DEBUG: Attempting to load project config from {current_working_directory}/.algokit.toml DEBUG: No .algokit.toml file found in the project directory. diff --git a/tests/version_check/test_version_check.test_version_check_queries_github_when_no_cache.approved.txt b/tests/version_check/test_version_check.test_version_check_queries_github_when_no_cache.approved.txt index a4a25d09..c4dbc075 100644 --- a/tests/version_check/test_version_check.test_version_check_queries_github_when_no_cache.approved.txt +++ b/tests/version_check/test_version_check.test_version_check_queries_github_when_no_cache.approved.txt @@ -1,6 +1,6 @@ DEBUG: {app_state}/last-version-check inaccessible DEBUG: HTTP Request: GET https://api.github.com/repos/algorandfoundation/algokit-cli/releases/latest "HTTP/1.1 200 OK" DEBUG: Latest version tag: v{new_version} -You are using AlgoKit version {current_version}, however version {new_version} is available. +You are using AlgoKit version {current_version}, however version {new_version} is available. Please update using the tool used to install AlgoKit. DEBUG: Attempting to load project config from {current_working_directory}/.algokit.toml DEBUG: No .algokit.toml file found in the project directory. diff --git a/tests/version_check/test_version_check.test_version_check_uses_cache.approved.txt b/tests/version_check/test_version_check.test_version_check_uses_cache.approved.txt index c7990176..ce57ae48 100644 --- a/tests/version_check/test_version_check.test_version_check_uses_cache.approved.txt +++ b/tests/version_check/test_version_check.test_version_check_uses_cache.approved.txt @@ -1,4 +1,4 @@ DEBUG: 1234.56.78 found in cache {app_state}/last-version-check -You are using AlgoKit version {current_version}, however version 1234.56.78 is available. +You are using AlgoKit version {current_version}, however version 1234.56.78 is available. Please update using the tool used to install AlgoKit. DEBUG: Attempting to load project config from {current_working_directory}/.algokit.toml DEBUG: No .algokit.toml file found in the project directory.