Skip to content

Commit f609cfb

Browse files
feat: inform user about cli updates (#91)
Co-authored-by: aviatco <32952699+aviatco@users.noreply.github.com>
1 parent a8cedcf commit f609cfb

7 files changed

Lines changed: 360 additions & 3 deletions

File tree

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
kind: added
2+
body: Display a notification to users on login when a new fab cli version is available
3+
time: 2025-12-11T18:34:25.601088227+01:00

docs/essentials/settings.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ The Fabric CLI provides a comprehensive set of configuration settings that allow
77
| Name | Description | Type | Default |
88
|--------------------------------|-------------------------------------------------------------------------------------------- |------------|---------|
99
| `cache_enabled` | Toggles caching of CLI HTTP responses | `BOOLEAN` | `true` |
10+
| `check_cli_version_updates` | Enables automatic update notifications on login | `BOOLEAN` | `true` |
1011
| `debug_enabled` | Toggles additional diagnostic logs for troubleshooting | `BOOLEAN` | `false` |
1112
| `context_persistence_enabled` | Persists CLI navigation context in command line mode across sessions | `BOOLEAN` | `false` |
1213
| `encryption_fallback_enabled` | Permits storing tokens in plain text if secure encryption is unavailable | `BOOLEAN` | `false` |

src/fabric_cli/commands/auth/fab_auth.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
from fabric_cli.core.fab_exceptions import FabricCLIError
1111
from fabric_cli.errors import ErrorMessages
1212
from fabric_cli.utils import fab_mem_store as utils_mem_store
13-
from fabric_cli.utils import fab_ui
13+
from fabric_cli.utils import fab_ui, fab_version_check
1414

1515

1616
def init(args: Namespace) -> Any:
@@ -199,7 +199,10 @@ def init(args: Namespace) -> Any:
199199
except KeyboardInterrupt:
200200
# User cancelled the authentication process
201201
return False
202-
return True
202+
203+
fab_version_check.check_and_notify_update()
204+
205+
return True
203206

204207

205208
def logout(args: Namespace) -> None:

src/fabric_cli/core/fab_constant.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,11 @@
9191
FAB_OUTPUT_FORMAT = "output_format"
9292
FAB_FOLDER_LISTING_ENABLED = "folder_listing_enabled"
9393
FAB_WS_PRIVATE_LINKS_ENABLED = "workspace_private_links_enabled"
94+
FAB_CHECK_UPDATES = "check_cli_version_updates"
95+
96+
# Version check settings
97+
VERSION_CHECK_PYPI_URL = "https://pypi.org/pypi/ms-fabric-cli/json"
98+
VERSION_CHECK_TIMEOUT_SECONDS = 3
9499

95100
FAB_CONFIG_KEYS_TO_VALID_VALUES = {
96101
FAB_CACHE_ENABLED: ["false", "true"],
@@ -111,6 +116,7 @@
111116
FAB_OUTPUT_FORMAT: ["text", "json"],
112117
FAB_FOLDER_LISTING_ENABLED: ["false", "true"],
113118
FAB_WS_PRIVATE_LINKS_ENABLED: ["false", "true"],
119+
FAB_CHECK_UPDATES: ["false", "true"],
114120
# Add more keys and their respective allowed values as needed
115121
}
116122

@@ -127,6 +133,7 @@
127133
FAB_OUTPUT_FORMAT: "text",
128134
FAB_FOLDER_LISTING_ENABLED: "false",
129135
FAB_WS_PRIVATE_LINKS_ENABLED: "false",
136+
FAB_CHECK_UPDATES: "true",
130137
}
131138

132139
# Command descriptions
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
# Copyright (c) Microsoft Corporation.
2+
# Licensed under the MIT License.
3+
4+
"""
5+
Version update checking for Fabric CLI.
6+
7+
This module checks PyPI for newer versions of ms-fabric-cli and displays
8+
a notification to the user if an update is available.
9+
"""
10+
11+
from typing import Optional
12+
13+
import requests
14+
15+
from fabric_cli import __version__
16+
from fabric_cli.core import fab_constant, fab_logger, fab_state_config
17+
from fabric_cli.utils import fab_ui
18+
19+
20+
def _fetch_latest_version_from_pypi() -> Optional[str]:
21+
"""
22+
Fetch the latest version from PyPI JSON API.
23+
24+
Returns:
25+
Latest version string if successful, None otherwise.
26+
"""
27+
try:
28+
response = requests.get(
29+
fab_constant.VERSION_CHECK_PYPI_URL,
30+
timeout=fab_constant.VERSION_CHECK_TIMEOUT_SECONDS
31+
)
32+
response.raise_for_status()
33+
return response.json()["info"]["version"]
34+
except (requests.RequestException, KeyError, ValueError, TypeError) as e:
35+
# Silently fail - don't interrupt user experience for version checks
36+
fab_logger.log_debug(f"Failed to fetch version from PyPI: {e}")
37+
return None
38+
39+
40+
def _is_pypi_version_newer(pypi_version: str) -> bool:
41+
"""
42+
Compare PyPI version with current version to determine if an update is available.
43+
44+
Args:
45+
pypi_version: Version string from PyPI
46+
47+
Returns:
48+
True if PyPI version is newer than current installed version
49+
"""
50+
try:
51+
# Parse versions as tuples (e.g., "1.3.0" -> (1, 3, 0))
52+
current_parts = tuple(int(x) for x in __version__.split("."))
53+
pypi_parts = tuple(int(x) for x in pypi_version.split("."))
54+
return pypi_parts > current_parts
55+
except (ValueError, AttributeError):
56+
# Conservative: don't show notification version could not be parsed
57+
return False
58+
59+
60+
def check_and_notify_update() -> None:
61+
"""
62+
Check for CLI updates and display notification if a newer version is available.
63+
64+
This function:
65+
- Respects user's check_updates config setting
66+
- Checks PyPI on every login for the latest version
67+
- Displays notification if an update is available
68+
- Fails silently if PyPI is unreachable
69+
"""
70+
check_enabled = fab_state_config.get_config(fab_constant.FAB_CHECK_UPDATES)
71+
if check_enabled == "false":
72+
fab_logger.log_debug("Version check disabled by user configuration")
73+
return
74+
75+
fab_logger.log_debug("Checking PyPI for latest version")
76+
latest_version = _fetch_latest_version_from_pypi()
77+
78+
if latest_version and _is_pypi_version_newer(latest_version):
79+
msg = (
80+
f"\n[notice] A new release of fab is available: {__version__}{latest_version}\n"
81+
"[notice] To update, run: pip install --upgrade ms-fabric-cli\n"
82+
)
83+
fab_ui.print_grey(msg)
84+
elif latest_version:
85+
fab_logger.log_debug(f"Already on latest version: {__version__}")
86+
else:
87+
fab_logger.log_debug("Could not fetch latest version from PyPI")

tests/test_commands/conftest.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,7 @@ def vcr_instance(vcr_mode, request):
104104
before_record_response=process_response,
105105
path_transformer=vcr.VCR.ensure_suffix(".yaml"),
106106
match_on=["method", "uri", "json_body"],
107-
ignore_hosts=["login.microsoftonline.com"],
107+
ignore_hosts=["login.microsoftonline.com", "pypi.org"],
108108
)
109109

110110
set_vcr_mode_env(vcr_mode)

0 commit comments

Comments
 (0)