Script 'mail_helper' called by obssrc
Hello community,

here is the log from the commit of package azure-cli-core for openSUSE:Factory 
checked in at 2026-03-04 21:07:14
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/azure-cli-core (Old)
 and      /work/SRC/openSUSE:Factory/.azure-cli-core.new.561 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Package is "azure-cli-core"

Wed Mar  4 21:07:14 2026 rev:93 rq:1336190 version:2.84.0

Changes:
--------
--- /work/SRC/openSUSE:Factory/azure-cli-core/azure-cli-core.changes    
2026-02-17 18:29:21.426931021 +0100
+++ /work/SRC/openSUSE:Factory/.azure-cli-core.new.561/azure-cli-core.changes   
2026-03-04 21:07:59.955341477 +0100
@@ -1,0 +2,8 @@
+Tue Mar  3 07:12:42 UTC 2026 - John Paul Adrian Glaubitz 
<[email protected]>
+
+- New upstream release
+  + Version 2.84.0
+  + For detailed information about changes see the
+    HISTORY.rst file provided with this package
+
+-------------------------------------------------------------------

Old:
----
  azure_cli_core-2.83.0.tar.gz

New:
----
  azure_cli_core-2.84.0.tar.gz

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Other differences:
------------------
++++++ azure-cli-core.spec ++++++
--- /var/tmp/diff_new_pack.t7tyHJ/_old  2026-03-04 21:08:00.779375629 +0100
+++ /var/tmp/diff_new_pack.t7tyHJ/_new  2026-03-04 21:08:00.783375795 +0100
@@ -24,7 +24,7 @@
 %global _sitelibdir %{%{pythons}_sitelib}
 
 Name:           azure-cli-core
-Version:        2.83.0
+Version:        2.84.0
 Release:        0
 Summary:        Microsoft Azure CLI Core Module
 License:        MIT

++++++ azure_cli_core-2.83.0.tar.gz -> azure_cli_core-2.84.0.tar.gz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/azure_cli_core-2.83.0/HISTORY.rst 
new/azure_cli_core-2.84.0/HISTORY.rst
--- old/azure_cli_core-2.83.0/HISTORY.rst       2026-01-27 08:23:53.000000000 
+0100
+++ new/azure_cli_core-2.84.0/HISTORY.rst       2026-02-25 03:35:23.000000000 
+0100
@@ -3,6 +3,10 @@
 Release History
 ===============
 
+2.84.0
+++++++
+* Minor fixes
+
 2.83.0
 ++++++
 * Resolve CVE-2025-69277 (#32610)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/azure_cli_core-2.83.0/PKG-INFO 
new/azure_cli_core-2.84.0/PKG-INFO
--- old/azure_cli_core-2.83.0/PKG-INFO  2026-01-27 08:24:42.918434900 +0100
+++ new/azure_cli_core-2.84.0/PKG-INFO  2026-02-25 03:36:22.626516000 +0100
@@ -1,6 +1,6 @@
 Metadata-Version: 2.4
 Name: azure-cli-core
-Version: 2.83.0
+Version: 2.84.0
 Summary: Microsoft Azure Command-Line Tools Core Module
 Home-page: https://github.com/Azure/azure-cli
 Author: Microsoft Corporation
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/azure_cli_core-2.83.0/azure/cli/core/__init__.py 
new/azure_cli_core-2.84.0/azure/cli/core/__init__.py
--- old/azure_cli_core-2.83.0/azure/cli/core/__init__.py        2026-01-27 
08:23:53.000000000 +0100
+++ new/azure_cli_core-2.84.0/azure/cli/core/__init__.py        2026-02-25 
03:35:23.000000000 +0100
@@ -4,11 +4,13 @@
 # 
--------------------------------------------------------------------------------------------
 # pylint: disable=line-too-long
 
-__version__ = "2.83.0"
+__version__ = "2.84.0"
 
 import os
 import sys
 import timeit
+import concurrent.futures
+from concurrent.futures import ThreadPoolExecutor
 
 from knack.cli import CLI
 from knack.commands import CLICommandsLoader
@@ -34,6 +36,10 @@
 ALWAYS_LOADED_MODULES = []
 # Extensions that will always be loaded if installed. They don't expose 
commands but hook into CLI core.
 ALWAYS_LOADED_EXTENSIONS = ['azext_ai_examples', 'azext_next']
+# Timeout (in seconds) for loading a single module. Acts as a safety valve to 
prevent indefinite hangs
+MODULE_LOAD_TIMEOUT_SECONDS = 60
+# Maximum number of worker threads for parallel module loading.
+MAX_WORKER_THREAD_COUNT = 4
 
 
 def _configure_knack():
@@ -197,6 +203,17 @@
         format_styled_text.theme = theme
 
 
+class ModuleLoadResult:  # pylint: disable=too-few-public-methods
+    def __init__(self, module_name, command_table, group_table, elapsed_time, 
error=None, traceback_str=None, command_loader=None):
+        self.module_name = module_name
+        self.command_table = command_table
+        self.group_table = group_table
+        self.elapsed_time = elapsed_time
+        self.error = error
+        self.traceback_str = traceback_str
+        self.command_loader = command_loader
+
+
 class MainCommandsLoader(CLICommandsLoader):
 
     # Format string for pretty-print the command module table
@@ -241,11 +258,11 @@
         import pkgutil
         import traceback
         from azure.cli.core.commands import (
-            _load_module_command_loader, _load_extension_command_loader, 
BLOCKED_MODS, ExtensionCommandSource)
+            _load_extension_command_loader, ExtensionCommandSource)
         from azure.cli.core.extension import (
             get_extensions, get_extension_path, get_extension_modname)
         from azure.cli.core.breaking_change import (
-            import_core_breaking_changes, import_module_breaking_changes, 
import_extension_breaking_changes)
+            import_core_breaking_changes, import_extension_breaking_changes)
 
         def _update_command_table_from_modules(args, command_modules=None):
             """Loads command tables from modules and merge into the main 
command table.
@@ -273,41 +290,17 @@
                 except ImportError as e:
                     logger.warning(e)
 
-            count = 0
-            cumulative_elapsed_time = 0
-            cumulative_group_count = 0
-            cumulative_command_count = 0
-            logger.debug("Loading command modules:")
-            logger.debug(self.header_mod)
+            start_time = timeit.default_timer()
+            logger.debug("Loading command modules...")
+            results = self._load_modules(args, command_modules)
 
-            for mod in [m for m in command_modules if m not in BLOCKED_MODS]:
-                try:
-                    start_time = timeit.default_timer()
-                    module_command_table, module_group_table = 
_load_module_command_loader(self, args, mod)
-                    import_module_breaking_changes(mod)
-                    for cmd in module_command_table.values():
-                        cmd.command_source = mod
-                    self.command_table.update(module_command_table)
-                    self.command_group_table.update(module_group_table)
+            count, cumulative_group_count, cumulative_command_count = \
+                self._process_results_with_timing(results)
 
-                    elapsed_time = timeit.default_timer() - start_time
-                    logger.debug(self.item_format_string, mod, elapsed_time,
-                                 len(module_group_table), 
len(module_command_table))
-                    count += 1
-                    cumulative_elapsed_time += elapsed_time
-                    cumulative_group_count += len(module_group_table)
-                    cumulative_command_count += len(module_command_table)
-                except Exception as ex:  # pylint: disable=broad-except
-                    # Changing this error message requires updating CI script 
that checks for failed
-                    # module loading.
-                    from azure.cli.core import telemetry
-                    logger.error("Error loading command module '%s': %s", mod, 
ex)
-                    telemetry.set_exception(exception=ex, 
fault_type='module-load-error-' + mod,
-                                            summary='Error loading module: 
{}'.format(mod))
-                    logger.debug(traceback.format_exc())
+            total_elapsed_time = timeit.default_timer() - start_time
             # Summary line
             logger.debug(self.item_format_string,
-                         "Total ({})".format(count), cumulative_elapsed_time,
+                         "Total ({})".format(count), total_elapsed_time,
                          cumulative_group_count, cumulative_command_count)
 
         def _update_command_table_from_extensions(ext_suppressions, 
extension_modname=None):
@@ -345,70 +338,80 @@
                 return filtered_extensions
 
             extensions = get_extensions()
-            if extensions:
-                if extension_modname is not None:
-                    extension_modname.extend(ALWAYS_LOADED_EXTENSIONS)
-                    extensions = _filter_modname(extensions)
-                allowed_extensions = _handle_extension_suppressions(extensions)
-                module_commands = set(self.command_table.keys())
-
-                count = 0
-                cumulative_elapsed_time = 0
-                cumulative_group_count = 0
-                cumulative_command_count = 0
-                logger.debug("Loading extensions:")
-                logger.debug(self.header_ext)
+            if not extensions:
+                return
 
-                for ext in allowed_extensions:
-                    try:
-                        # Import in the `for` loop because 
`allowed_extensions` can be []. In such case we
-                        # don't need to import `check_version_compatibility` 
at all.
-                        from azure.cli.core.extension.operations import 
check_version_compatibility
-                        check_version_compatibility(ext.get_metadata())
-                    except CLIError as ex:
-                        # issue warning and skip loading extensions that 
aren't compatible with the CLI core
-                        logger.warning(ex)
-                        continue
-                    ext_name = ext.name
-                    ext_dir = ext.path or get_extension_path(ext_name)
-                    sys.path.append(ext_dir)
-                    try:
-                        ext_mod = get_extension_modname(ext_name, 
ext_dir=ext_dir)
-                        # Add to the map. This needs to happen before we load 
commands as registering a command
-                        # from an extension requires this map to be up-to-date.
-                        # self._mod_to_ext_map[ext_mod] = ext_name
-                        start_time = timeit.default_timer()
-                        extension_command_table, extension_group_table = \
-                            _load_extension_command_loader(self, args, ext_mod)
-                        import_extension_breaking_changes(ext_mod)
-
-                        for cmd_name, cmd in extension_command_table.items():
-                            cmd.command_source = ExtensionCommandSource(
-                                extension_name=ext_name,
-                                overrides_command=cmd_name in module_commands,
-                                preview=ext.preview,
-                                experimental=ext.experimental)
-
-                        self.command_table.update(extension_command_table)
-                        self.command_group_table.update(extension_group_table)
-
-                        elapsed_time = timeit.default_timer() - start_time
-                        logger.debug(self.item_ext_format_string, ext_name, 
elapsed_time,
-                                     len(extension_group_table), 
len(extension_command_table),
-                                     ext_dir)
-                        count += 1
-                        cumulative_elapsed_time += elapsed_time
-                        cumulative_group_count += len(extension_group_table)
-                        cumulative_command_count += 
len(extension_command_table)
-                    except Exception as ex:  # pylint: disable=broad-except
-                        self.cli_ctx.raise_event(EVENT_FAILED_EXTENSION_LOAD, 
extension_name=ext_name)
-                        logger.warning("Unable to load extension '%s: %s'. Use 
--debug for more information.",
-                                       ext_name, ex)
-                        logger.debug(traceback.format_exc())
-                # Summary line
-                logger.debug(self.item_ext_format_string,
-                             "Total ({})".format(count), 
cumulative_elapsed_time,
-                             cumulative_group_count, cumulative_command_count, 
"")
+            if extension_modname is not None:
+                extension_modname.extend(ALWAYS_LOADED_EXTENSIONS)
+                extensions = _filter_modname(extensions)
+            allowed_extensions = _handle_extension_suppressions(extensions)
+            module_commands = set(self.command_table.keys())
+
+            count = 0
+            cumulative_elapsed_time = 0
+            cumulative_group_count = 0
+            cumulative_command_count = 0
+            logger.debug("Loading extensions:")
+            logger.debug(self.header_ext)
+
+            for ext in allowed_extensions:
+                try:
+                    # Import in the `for` loop because `allowed_extensions` 
can be []. In such case we
+                    # don't need to import `check_version_compatibility` at 
all.
+                    from azure.cli.core.extension.operations import 
check_version_compatibility
+                    check_version_compatibility(ext.get_metadata())
+                except CLIError as ex:
+                    # issue warning and skip loading extensions that aren't 
compatible with the CLI core
+                    logger.warning(ex)
+                    continue
+                ext_name = ext.name
+                ext_dir = ext.path or get_extension_path(ext_name)
+                sys.path.append(ext_dir)
+                try:
+                    ext_mod = get_extension_modname(ext_name, ext_dir=ext_dir)
+                    # Add to the map. This needs to happen before we load 
commands as registering a command
+                    # from an extension requires this map to be up-to-date.
+                    # self._mod_to_ext_map[ext_mod] = ext_name
+                    start_time = timeit.default_timer()
+                    extension_command_table, extension_group_table, 
extension_command_loader = \
+                        _load_extension_command_loader(self, args, ext_mod)
+                    import_extension_breaking_changes(ext_mod)
+
+                    for cmd_name, cmd in extension_command_table.items():
+                        cmd.command_source = ExtensionCommandSource(
+                            extension_name=ext_name,
+                            overrides_command=cmd_name in module_commands,
+                            preview=ext.preview,
+                            experimental=ext.experimental)
+
+                    # Populate cmd_to_loader_map for extension commands
+                    if extension_command_loader:
+                        self.loaders.append(extension_command_loader)
+                        for cmd_name in extension_command_table:
+                            if cmd_name not in self.cmd_to_loader_map:
+                                self.cmd_to_loader_map[cmd_name] = []
+                            
self.cmd_to_loader_map[cmd_name].append(extension_command_loader)
+
+                    self.command_table.update(extension_command_table)
+                    self.command_group_table.update(extension_group_table)
+
+                    elapsed_time = timeit.default_timer() - start_time
+                    logger.debug(self.item_ext_format_string, ext_name, 
elapsed_time,
+                                 len(extension_group_table), 
len(extension_command_table),
+                                 ext_dir)
+                    count += 1
+                    cumulative_elapsed_time += elapsed_time
+                    cumulative_group_count += len(extension_group_table)
+                    cumulative_command_count += len(extension_command_table)
+                except Exception as ex:  # pylint: disable=broad-except
+                    self.cli_ctx.raise_event(EVENT_FAILED_EXTENSION_LOAD, 
extension_name=ext_name)
+                    logger.warning("Unable to load extension '%s: %s'. Use 
--debug for more information.",
+                                   ext_name, ex)
+                    logger.debug(traceback.format_exc())
+            # Summary line
+            logger.debug(self.item_ext_format_string,
+                         "Total ({})".format(count), cumulative_elapsed_time,
+                         cumulative_group_count, cumulative_command_count, "")
 
         def _wrap_suppress_extension_func(func, ext):
             """ Wrapper method to handle centralization of log messages for 
extension filters """
@@ -449,6 +452,7 @@
         command_index = None
         # Set fallback=False to turn off command index in case of regression
         use_command_index = self.cli_ctx.config.getboolean('core', 
'use_command_index', fallback=True)
+
         if use_command_index:
             command_index = CommandIndex(self.cli_ctx)
             index_result = command_index.get(args)
@@ -522,9 +526,39 @@
 
         if use_command_index:
             command_index.update(self.command_table)
+            self._cache_help_index(command_index)
 
         return self.command_table
 
+    def _display_cached_help(self, help_data, command_path='root'):
+        """Display help from cached help index without loading modules."""
+        # Delegate to the help system for consistent formatting
+        self.cli_ctx.invocation.help.show_cached_help(help_data, command_path)
+
+    def _cache_help_index(self, command_index):
+        """Cache help summary for top-level (root) help only."""
+        try:
+            from azure.cli.core.parser import AzCliCommandParser
+            from azure.cli.core._help import CliGroupHelpFile, 
extract_help_index_data
+
+            parser = AzCliCommandParser(self.cli_ctx)
+            parser.load_command_table(self)
+
+            subparser = parser.subparsers.get(tuple())
+            if subparser:
+                help_file = CliGroupHelpFile(self.cli_ctx.invocation.help, '', 
subparser)
+                help_file.load(subparser)
+
+                groups, commands = extract_help_index_data(help_file)
+
+                if groups or commands:
+                    help_index_data = {'groups': groups, 'commands': commands}
+                    command_index.set_help_index(help_index_data)
+                    logger.debug("Cached top-level help with %d groups and %d 
commands", len(groups), len(commands))
+
+        except Exception as ex:  # pylint: disable=broad-except
+            logger.debug("Failed to cache help data: %s", ex)
+
     @staticmethod
     def _sort_command_loaders(command_loaders):
         module_command_loaders = []
@@ -587,12 +621,115 @@
                 
self.extra_argument_registry.update(loader.extra_argument_registry)
                 loader._update_command_definitions()  # pylint: 
disable=protected-access
 
+    def _load_modules(self, args, command_modules):
+        """Load command modules using ThreadPoolExecutor with timeout 
protection."""
+        from azure.cli.core.commands import BLOCKED_MODS
+
+        results = []
+        with ThreadPoolExecutor(max_workers=MAX_WORKER_THREAD_COUNT) as 
executor:
+            future_to_module = {executor.submit(self._load_single_module, mod, 
args): mod
+                                for mod in command_modules if mod not in 
BLOCKED_MODS}
+
+            try:
+                for future in 
concurrent.futures.as_completed(future_to_module, 
timeout=MODULE_LOAD_TIMEOUT_SECONDS):
+                    try:
+                        result = future.result()
+                        results.append(result)
+                    except (ImportError, AttributeError, TypeError, 
ValueError) as ex:
+                        mod = future_to_module[future]
+                        logger.warning("Module '%s' load failed: %s", mod, ex)
+                        results.append(ModuleLoadResult(mod, {}, {}, 0, ex))
+                    except Exception as ex:  # pylint: 
disable=broad-exception-caught
+                        mod = future_to_module[future]
+                        logger.warning("Module '%s' load failed with 
unexpected exception: %s", mod, ex)
+                        results.append(ModuleLoadResult(mod, {}, {}, 0, ex))
+            except concurrent.futures.TimeoutError:
+                for future, mod in future_to_module.items():
+                    if future.done():
+                        try:
+                            result = future.result()
+                            results.append(result)
+                        except Exception as ex:  # pylint: 
disable=broad-exception-caught
+                            logger.warning("Module '%s' load failed: %s", mod, 
ex)
+                            results.append(ModuleLoadResult(mod, {}, {}, 0, 
ex))
+                    else:
+                        logger.warning("Module '%s' load timeout after %s 
seconds", mod, MODULE_LOAD_TIMEOUT_SECONDS)
+                        results.append(ModuleLoadResult(mod, {}, {}, 0,
+                                                        Exception(f"Module 
'{mod}' load timeout")))
+
+        return results
+
+    def _load_single_module(self, mod, args):
+        from azure.cli.core.breaking_change import 
import_module_breaking_changes
+        from azure.cli.core.commands import _load_module_command_loader
+        import traceback
+        try:
+            start_time = timeit.default_timer()
+            module_command_table, module_group_table, command_loader = 
_load_module_command_loader(self, args, mod)
+            import_module_breaking_changes(mod)
+            elapsed_time = timeit.default_timer() - start_time
+            return ModuleLoadResult(mod, module_command_table, 
module_group_table, elapsed_time, command_loader=command_loader)
+        except Exception as ex:  # pylint: disable=broad-except
+            tb_str = traceback.format_exc()
+            return ModuleLoadResult(mod, {}, {}, 0, ex, tb_str)
+
+    def _handle_module_load_error(self, result):
+        """Handle errors that occurred during module loading."""
+        from azure.cli.core import telemetry
+
+        logger.error("Error loading command module '%s': %s", 
result.module_name, result.error)
+        telemetry.set_exception(exception=result.error,
+                                fault_type='module-load-error-' + 
result.module_name,
+                                summary='Error loading module: 
{}'.format(result.module_name))
+        if result.traceback_str:
+            logger.debug(result.traceback_str)
+
+    def _process_successful_load(self, result):
+        """Process successfully loaded module results."""
+        if result.command_loader:
+            self.loaders.append(result.command_loader)
+
+            for cmd in result.command_table:
+                if cmd not in self.cmd_to_loader_map:
+                    self.cmd_to_loader_map[cmd] = []
+                self.cmd_to_loader_map[cmd].append(result.command_loader)
+
+        for cmd in result.command_table.values():
+            cmd.command_source = result.module_name
+
+        self.command_table.update(result.command_table)
+        self.command_group_table.update(result.group_table)
+
+        logger.debug(self.item_format_string, result.module_name, 
result.elapsed_time,
+                     len(result.group_table), len(result.command_table))
+
+    def _process_results_with_timing(self, results):
+        """Process pre-loaded module results with timing and progress 
reporting."""
+        logger.debug("Loaded command modules in parallel:")
+        logger.debug(self.header_mod)
+
+        count = 0
+        cumulative_group_count = 0
+        cumulative_command_count = 0
+
+        for result in results:
+            if result.error:
+                self._handle_module_load_error(result)
+            else:
+                self._process_successful_load(result)
+                count += 1
+                cumulative_group_count += len(result.group_table)
+                cumulative_command_count += len(result.command_table)
+
+        return count, cumulative_group_count, cumulative_command_count
+
 
 class CommandIndex:
 
     _COMMAND_INDEX = 'commandIndex'
     _COMMAND_INDEX_VERSION = 'version'
     _COMMAND_INDEX_CLOUD_PROFILE = 'cloudProfile'
+    _HELP_INDEX = 'helpIndex'
 
     def __init__(self, cli_ctx=None):
         """Class to manage command index.
@@ -606,6 +743,16 @@
             self.cloud_profile = cli_ctx.cloud.profile
         self.cli_ctx = cli_ctx
 
+    def _is_index_valid(self):
+        """Check if the command index version and cloud profile are valid.
+
+        :return: True if index is valid, False otherwise
+        """
+        index_version = self.INDEX.get(self._COMMAND_INDEX_VERSION)
+        cloud_profile = self.INDEX.get(self._COMMAND_INDEX_CLOUD_PROFILE)
+        return (index_version and index_version == self.version and
+                cloud_profile and cloud_profile == self.cloud_profile)
+
     def _get_top_level_completion_commands(self):
         """Get top-level command names for tab completion optimization.
 
@@ -631,10 +778,7 @@
         """
         # If the command index version or cloud profile doesn't match those of 
the current command,
         # invalidate the command index.
-        index_version = self.INDEX[self._COMMAND_INDEX_VERSION]
-        cloud_profile = self.INDEX[self._COMMAND_INDEX_CLOUD_PROFILE]
-        if not (index_version and index_version == self.version and
-                cloud_profile and cloud_profile == self.cloud_profile):
+        if not self._is_index_valid():
             logger.debug("Command index version or cloud profile is invalid or 
doesn't match the current command.")
             self.invalidate()
             return None
@@ -681,6 +825,28 @@
 
         return None
 
+    def get_help_index(self):
+        """Get the help index for top-level help display.
+
+        :return: Dictionary mapping top-level commands to their short 
summaries, or None if not available
+        """
+        if not self._is_index_valid():
+            return None
+
+        help_index = self.INDEX.get(self._HELP_INDEX, {})
+        if help_index:
+            logger.debug("Using cached help index with %d entries", 
len(help_index))
+            return help_index
+
+        return None
+
+    def set_help_index(self, help_data):
+        """Set the help index data.
+
+        :param help_data: Help index data structure containing groups and 
commands
+        """
+        self.INDEX[self._HELP_INDEX] = help_data
+
     def update(self, command_table):
         """Update the command index according to the given command table.
 
@@ -700,6 +866,7 @@
             module_name = command.loader.__module__
             if module_name not in index[top_command]:
                 index[top_command].append(module_name)
+
         elapsed_time = timeit.default_timer() - start_time
         self.INDEX[self._COMMAND_INDEX] = index
         logger.debug("Updated command index in %.3f seconds.", elapsed_time)
@@ -718,6 +885,7 @@
         self.INDEX[self._COMMAND_INDEX_VERSION] = ""
         self.INDEX[self._COMMAND_INDEX_CLOUD_PROFILE] = ""
         self.INDEX[self._COMMAND_INDEX] = {}
+        self.INDEX[self._HELP_INDEX] = {}
         logger.debug("Command index has been invalidated.")
 
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/azure_cli_core-2.83.0/azure/cli/core/_help.py 
new/azure_cli_core-2.84.0/azure/cli/core/_help.py
--- old/azure_cli_core-2.83.0/azure/cli/core/_help.py   2026-01-27 
08:23:53.000000000 +0100
+++ new/azure_cli_core-2.84.0/azure/cli/core/_help.py   2026-02-25 
03:35:23.000000000 +0100
@@ -46,6 +46,73 @@
 """
 
 
+def _get_tag_plain_text(tag_obj):
+    """Extract plain text from a tag object (typically ColorizedString).
+
+    ColorizedString objects store plain text in _message and add ANSI codes 
via __str__.
+    For caching, we need plain text only. This function safely extracts it.
+
+    :param tag_obj: Tag object (ColorizedString or other)
+    :return: Plain text string without ANSI codes
+    """
+    # ColorizedString stores plain text in _message attribute
+    if hasattr(tag_obj, '_message'):
+        return tag_obj._message  # pylint: disable=protected-access
+    # Fallback for non-ColorizedString objects
+    return str(tag_obj)
+
+
+def get_help_item_tags(item):
+    """Extract status tags from a help item (group or command).
+
+    Returns a space-separated string of plain text tags like '[Deprecated] 
[Preview]'.
+    """
+    tags = []
+    if hasattr(item, 'deprecate_info') and item.deprecate_info:
+        tag_obj = item.deprecate_info.tag
+        tags.append(_get_tag_plain_text(tag_obj))
+    if hasattr(item, 'preview_info') and item.preview_info:
+        tag_obj = item.preview_info.tag
+        tags.append(_get_tag_plain_text(tag_obj))
+    if hasattr(item, 'experimental_info') and item.experimental_info:
+        tag_obj = item.experimental_info.tag
+        tags.append(_get_tag_plain_text(tag_obj))
+    return ' '.join(tags)
+
+
+def extract_help_index_data(help_file):
+    """Extract groups and commands from help file children for caching.
+
+    Processes help file children and builds dictionaries of groups and commands
+    with their summaries and tags for top-level help display.
+
+    :param help_file: Help file with loaded children
+    :return: Tuple of (groups_dict, commands_dict)
+    """
+    groups = {}
+    commands = {}
+
+    for child in help_file.children:
+        if hasattr(child, 'name') and hasattr(child, 'short_summary'):
+            child_name = child.name
+            # Only include top-level items (no spaces in name)
+            if ' ' in child_name:
+                continue
+
+            tags = get_help_item_tags(child)
+            item_data = {
+                'summary': child.short_summary,
+                'tags': tags
+            }
+
+            if child.type == 'group':
+                groups[child_name] = item_data
+            else:
+                commands[child_name] = item_data
+
+    return groups, commands
+
+
 # PrintMixin class to decouple printing functionality from AZCLIHelp class.
 # Most of these methods override print methods in CLIHelp
 class CLIPrintMixin(CLIHelp):
@@ -241,6 +308,107 @@
     def update_examples(help_file):
         pass
 
+    @staticmethod
+    def _colorize_tag(tag_text, enable_color):
+        """Add color to a plain text tag based on its content."""
+        if not enable_color or not tag_text:
+            return tag_text
+
+        from knack.util import color_map
+
+        tag_lower = tag_text.lower()
+        if 'preview' in tag_lower:
+            color = color_map['preview']
+        elif 'experimental' in tag_lower:
+            color = color_map['experimental']
+        elif 'deprecat' in tag_lower:
+            color = color_map['deprecation']
+        else:
+            return tag_text
+
+        return f"{color}{tag_text}{color_map['reset']}"
+
+    @staticmethod
+    def _build_cached_help_items(data, enable_color=False):
+        """Process help items from cache and return list with calculated line 
lengths."""
+        from knack.help import _get_line_len
+        items = []
+        for name in sorted(data.keys()):
+            item = data[name]
+            plain_tags = item.get('tags', '')
+
+            # Colorize each tag individually if needed
+            if plain_tags and enable_color:
+                # Split multiple tags and colorize each
+                tag_parts = plain_tags.split()
+                colored_tags = ' '.join(AzCliHelp._colorize_tag(tag, 
enable_color) for tag in tag_parts)
+            else:
+                colored_tags = plain_tags
+
+            tags_len = len(plain_tags)
+            line_len = _get_line_len(name, tags_len)
+            items.append((name, colored_tags, line_len, item.get('summary', 
'')))
+        return items
+
+    @staticmethod
+    def _print_cached_help_section(items, header, max_line_len):
+        """Display cached help items with consistent formatting."""
+        from knack.help import FIRST_LINE_PREFIX, _get_hanging_indent, 
_get_padding_len
+        if not items:
+            return
+        print(f"\n{header}")
+        indent = 1
+        LINE_FORMAT = '{name}{padding}{tags}{separator}{summary}'
+        for name, tags, line_len, summary in items:
+            layout = {'line_len': line_len, 'tags': tags}
+            padding = ' ' * _get_padding_len(max_line_len, layout)
+            line = LINE_FORMAT.format(
+                name=name,
+                padding=padding,
+                tags=tags,
+                separator=FIRST_LINE_PREFIX if summary else '',
+                summary=summary
+            )
+            _print_indent(line, indent, _get_hanging_indent(max_line_len, 
indent))
+
+    def show_cached_help(self, help_data, args=None):
+        """Display help from cached help index without loading modules.
+
+        Args:
+            help_data: Cached help data dictionary
+            args: Original command line args. If empty/None, shows welcome 
banner.
+        """
+        ran_before = self.cli_ctx.config.getboolean('core', 'first_run', 
fallback=False)
+        if not ran_before:
+            print(PRIVACY_STATEMENT)
+            self.cli_ctx.config.set_value('core', 'first_run', 'yes')
+
+        if not args:
+            print(WELCOME_MESSAGE)
+
+        print("\nGroup")
+        print("    az")
+
+        groups_data = help_data.get('groups', {})
+        commands_data = help_data.get('commands', {})
+
+        groups_items = self._build_cached_help_items(groups_data, 
self.cli_ctx.enable_color)
+        commands_items = self._build_cached_help_items(commands_data, 
self.cli_ctx.enable_color)
+        max_line_len = max(
+            (line_len for _, _, line_len, _ in groups_items + commands_items),
+            default=0
+        )
+
+        self._print_cached_help_section(groups_items, "Subgroups:", 
max_line_len)
+        self._print_cached_help_section(commands_items, "Commands:", 
max_line_len)
+
+        # Use same az find message as non-cached path
+        print()  # Blank line before the message
+        self._print_az_find_message('')
+
+        from azure.cli.core.util import show_updates_available
+        show_updates_available(new_line_after=True)
+
 
 class CliHelpFile(KnackHelpFile):
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/azure_cli_core-2.83.0/azure/cli/core/commands/__init__.py 
new/azure_cli_core-2.84.0/azure/cli/core/commands/__init__.py
--- old/azure_cli_core-2.83.0/azure/cli/core/commands/__init__.py       
2026-01-27 08:23:53.000000000 +0100
+++ new/azure_cli_core-2.84.0/azure/cli/core/commands/__init__.py       
2026-02-25 03:35:23.000000000 +0100
@@ -518,6 +518,11 @@
         command_preserve_casing = roughly_parse_command_with_casing(args)
         args = _pre_command_table_create(self.cli_ctx, args)
 
+        if self._should_show_cached_help(args):
+            result = self._try_show_cached_help(command_preserve_casing, args)
+            if result:
+                return result
+
         self.cli_ctx.raise_event(EVENT_INVOKER_PRE_CMD_TBL_CREATE, args=args)
         self.commands_loader.load_command_table(args)
         self.cli_ctx.raise_event(EVENT_INVOKER_PRE_CMD_TBL_TRUNCATE,
@@ -579,6 +584,14 @@
             subparser = self.parser.subparsers[tuple()]
             self.help.show_welcome(subparser)
 
+            use_command_index = self._should_use_command_index()
+            logger.debug("About to cache help data, use_command_index=%s", 
use_command_index)
+            if use_command_index:
+                try:
+                    self._save_help_to_command_index(subparser)
+                except Exception as ex:  # pylint: disable=broad-except
+                    logger.debug("Failed to cache help data: %s", ex)
+
             # TODO: No event in base with which to target
             telemetry.set_command_details('az', 
command_preserve_casing=command_preserve_casing)
             telemetry.set_success(summary='welcome')
@@ -700,6 +713,68 @@
         return [(p.split('=', 1)[0] if p.startswith('--') else p[:2]) for p in 
args if
                 (p.startswith('-') and not p.startswith('---') and len(p) > 1)]
 
+    @staticmethod
+    def _is_top_level_help_request(args):
+        """Determine if this is a top-level help request (az --help or just 
az).
+
+        Returns True for both 'az' with no args and 'az --help' so we can use
+        cached data without loading all modules.
+        """
+        if not args:
+            return True
+
+        for arg in args:
+            if arg in ('--help', '-h', 'help'):
+                return True
+            if not arg.startswith('-'):
+                return False
+
+        return False
+
+    def _should_use_command_index(self):
+        """Check if command index optimization is enabled."""
+        return self.cli_ctx.config.getboolean('core', 'use_command_index', 
fallback=True)
+
+    def _should_show_cached_help(self, args):
+        return (self._should_use_command_index() and
+                self._is_top_level_help_request(args) and
+                not self.cli_ctx.data.get('completer_active'))
+
+    def _try_show_cached_help(self, command_preserve_casing, args):
+        """Try to show cached help for top-level help request.
+
+        Returns CommandResultItem if cached help was shown, None otherwise.
+        """
+        from azure.cli.core import CommandIndex
+        command_index = CommandIndex(self.cli_ctx)
+        help_index = command_index.get_help_index()
+
+        if help_index:
+            # Display cached help using the help system
+            self.help.show_cached_help(help_index, args)
+            telemetry.set_command_details('az', 
command_preserve_casing=command_preserve_casing, parameters=['--help'])
+            telemetry.set_success(summary='show help')
+            return CommandResultItem(None, exit_code=0)
+
+        return None
+
+    def _save_help_to_command_index(self, subparser):
+        """Extract help data from parser and save to command index for future 
fast access."""
+        from azure.cli.core import CommandIndex
+        from azure.cli.core._help import CliGroupHelpFile, 
extract_help_index_data
+
+        command_index = CommandIndex(self.cli_ctx)
+        help_file = CliGroupHelpFile(self.help, '', subparser)
+        help_file.load(subparser)
+
+        groups, commands = extract_help_index_data(help_file)
+
+        # Store in the command index
+        help_index_data = {'groups': groups, 'commands': commands}
+        if groups or commands:
+            command_index.set_help_index(help_index_data)
+            logger.debug("Cached %d groups and %d commands for fast access", 
len(groups), len(commands))
+
     def _run_job(self, expanded_arg, cmd_copy):
         params = self._filter_params(expanded_arg)
         try:
@@ -1134,22 +1209,17 @@
             logger.debug("Module '%s' is missing `get_command_loader` entry.", 
name)
 
     command_table = {}
+    command_loader = None
 
     if loader_cls:
         command_loader = loader_cls(cli_ctx=loader.cli_ctx)
-        loader.loaders.append(command_loader)  # This will be used by 
interactive
         if command_loader.supported_resource_type():
             command_table = command_loader.load_command_table(args)
-            if command_table:
-                for cmd in list(command_table.keys()):
-                    # TODO: If desired to for extension to patch module, this 
can be uncommented
-                    # if loader.cmd_to_loader_map.get(cmd):
-                    #    loader.cmd_to_loader_map[cmd].append(command_loader)
-                    # else:
-                    loader.cmd_to_loader_map[cmd] = [command_loader]
     else:
         logger.debug("Module '%s' is missing `COMMAND_LOADER_CLS` entry.", 
name)
-    return command_table, command_loader.command_group_table
+
+    group_table = command_loader.command_group_table if command_loader else {}
+    return command_table, group_table, command_loader
 
 
 def _load_extension_command_loader(loader, args, ext):
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/azure_cli_core-2.83.0/azure/cli/core/parser.py 
new/azure_cli_core-2.84.0/azure/cli/core/parser.py
--- old/azure_cli_core-2.83.0/azure/cli/core/parser.py  2026-01-27 
08:23:53.000000000 +0100
+++ new/azure_cli_core-2.84.0/azure/cli/core/parser.py  2026-02-25 
03:35:23.000000000 +0100
@@ -186,6 +186,7 @@
 
         telemetry.set_command_details(
             command=self.prog[3:],
+            parameters=['--help'],
             extension_name=extension_name,
             extension_version=extension_version)
         telemetry.set_success(summary='show help')
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/azure_cli_core-2.83.0/azure/cli/core/profiles/_shared.py 
new/azure_cli_core-2.84.0/azure/cli/core/profiles/_shared.py
--- old/azure_cli_core-2.83.0/azure/cli/core/profiles/_shared.py        
2026-01-27 08:23:53.000000000 +0100
+++ new/azure_cli_core-2.84.0/azure/cli/core/profiles/_shared.py        
2026-02-25 03:35:23.000000000 +0100
@@ -218,7 +218,7 @@
         ResourceType.MGMT_IOTHUB: None,
         ResourceType.MGMT_IOTDPS: None,
         ResourceType.MGMT_IOTCENTRAL: None,
-        ResourceType.MGMT_ARO: '2023-11-22',
+        ResourceType.MGMT_ARO: None,
         ResourceType.MGMT_DATABOXEDGE: '2021-02-01-preview',
         ResourceType.MGMT_CUSTOMLOCATION: '2021-03-15-preview',
         ResourceType.MGMT_CONTAINERSERVICE: None,
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/azure_cli_core-2.83.0/azure_cli_core.egg-info/PKG-INFO 
new/azure_cli_core-2.84.0/azure_cli_core.egg-info/PKG-INFO
--- old/azure_cli_core-2.83.0/azure_cli_core.egg-info/PKG-INFO  2026-01-27 
08:24:42.000000000 +0100
+++ new/azure_cli_core-2.84.0/azure_cli_core.egg-info/PKG-INFO  2026-02-25 
03:36:22.000000000 +0100
@@ -1,6 +1,6 @@
 Metadata-Version: 2.4
 Name: azure-cli-core
-Version: 2.83.0
+Version: 2.84.0
 Summary: Microsoft Azure Command-Line Tools Core Module
 Home-page: https://github.com/Azure/azure-cli
 Author: Microsoft Corporation
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/azure_cli_core-2.83.0/setup.py 
new/azure_cli_core-2.84.0/setup.py
--- old/azure_cli_core-2.83.0/setup.py  2026-01-27 08:23:53.000000000 +0100
+++ new/azure_cli_core-2.84.0/setup.py  2026-02-25 03:35:23.000000000 +0100
@@ -8,7 +8,7 @@
 from codecs import open
 from setuptools import setup, find_packages
 
-VERSION = "2.83.0"
+VERSION = "2.84.0"
 
 # If we have source, validate that our version numbers match
 # This should prevent uploading releases with mismatched versions.

Reply via email to