diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index 526b1643..8e81dbed 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -52,6 +52,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 already-saved server can be made the default without re-adding it using `rsconnect server set-default -n `. The new `rsconnect server` command group also aliases `add`, `list`, `remove`, `details`, and `bootstrap`. + These commands are now listed under `rsconnect server` in `rsconnect --help`; + the top-level forms (`rsconnect add`, etc.) continue to work unchanged. - New `environment` subcommand for managing execution environments on Connect. - New `integration` subcommand for managing OAuth integrations on Connect. diff --git a/docs/commands/add.md b/docs/commands/add.md index 0cb4ef98..deaba81f 100644 --- a/docs/commands/add.md +++ b/docs/commands/add.md @@ -1,3 +1,4 @@ ::: mkdocs-click :module: rsconnect.main :command: add + :prog_name: rsconnect server add diff --git a/docs/commands/bootstrap.md b/docs/commands/bootstrap.md index 49a5ecf1..093f6237 100644 --- a/docs/commands/bootstrap.md +++ b/docs/commands/bootstrap.md @@ -1,3 +1,4 @@ ::: mkdocs-click :module: rsconnect.main :command: bootstrap + :prog_name: rsconnect server bootstrap diff --git a/docs/commands/details.md b/docs/commands/details.md index 737d8ba2..481daf48 100644 --- a/docs/commands/details.md +++ b/docs/commands/details.md @@ -1,3 +1,4 @@ ::: mkdocs-click :module: rsconnect.main :command: details + :prog_name: rsconnect server details diff --git a/docs/commands/list.md b/docs/commands/list.md index fe6a4ff2..8459e8ae 100644 --- a/docs/commands/list.md +++ b/docs/commands/list.md @@ -1,3 +1,4 @@ ::: mkdocs-click :module: rsconnect.main :command: list_servers + :prog_name: rsconnect server list diff --git a/docs/commands/remove.md b/docs/commands/remove.md index 06b5a234..315d501a 100644 --- a/docs/commands/remove.md +++ b/docs/commands/remove.md @@ -1,3 +1,4 @@ ::: mkdocs-click :module: rsconnect.main :command: remove + :prog_name: rsconnect server remove diff --git a/mkdocs.yml b/mkdocs.yml index c72e834f..8431e4ec 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -39,20 +39,21 @@ nav: - Server Administration: server-administration.md - CLI reference: - rsconnect: - - add: commands/add.md - - bootstrap: commands/bootstrap.md - content: commands/content.md - deploy: commands/deploy.md - - details: commands/details.md - environment: commands/environment.md - info: commands/info.md - integration: commands/integration.md - - list: commands/list.md - login: commands/login.md - logout: commands/logout.md - quickstart: commands/quickstart.md - - remove: commands/remove.md - - set-default: commands/set-default.md + - server: + - add: commands/add.md + - bootstrap: commands/bootstrap.md + - details: commands/details.md + - list: commands/list.md + - remove: commands/remove.md + - set-default: commands/set-default.md - system: commands/system.md - version: commands/version.md - write-manifest: commands/write-manifest.md diff --git a/rsconnect/main.py b/rsconnect/main.py index fc4efe9c..cc30ef98 100644 --- a/rsconnect/main.py +++ b/rsconnect/main.py @@ -530,7 +530,27 @@ def wrapper(*args: P.args, **kwargs: P.kwargs): return wrapper -@click.group(no_args_is_help=True) +class ServerAliasHidingGroup(click.Group): + """Root command group that omits the server-management commands from the + top-level ``--help`` listing. + + ``add``, ``list``, ``remove``, and ``details`` are also registered under + the ``server`` group, which is their advertised home. They remain fully + invokable at the top level (e.g. ``rsconnect add``) and are still offered + by shell completion; this only declutters ``rsconnect --help``. + """ + + def format_commands(self, ctx: click.Context, formatter: click.HelpFormatter) -> None: + aliased = set(server_group.commands) + original = self.commands + self.commands = {name: cmd for name, cmd in original.items() if name not in aliased} + try: + super().format_commands(ctx, formatter) + finally: + self.commands = original + + +@click.group(cls=ServerAliasHidingGroup, no_args_is_help=True) @click.option("--future", "-u", is_flag=True, hidden=True, help="Enables future functionality.") def cli(future: bool): """ diff --git a/tests/test_main.py b/tests/test_main.py index 3afe0784..747e4893 100644 --- a/tests/test_main.py +++ b/tests/test_main.py @@ -1928,6 +1928,37 @@ def test_top_level_list_still_works(self, tmp_path): assert result.exit_code == 0, result.output assert "s1" in result.output + @staticmethod + def _command_names(help_output): + # Click renders each command row with a 2-space indent; wrapped + # help text is indented further, so match only the command rows. + section = help_output.split("Commands:", 1)[1] + return [ + line.strip().split()[0] + for line in section.splitlines() + if line.startswith(" ") and not line.startswith(" ") and line.strip() + ] + + def test_server_aliases_hidden_from_top_level_help(self): + runner = CliRunner() + result = runner.invoke(cli, ["--help"]) + assert result.exit_code == 0, result.output + names = self._command_names(result.output) + assert "server" in names + for hidden in ("add", "list", "remove", "details", "bootstrap"): + assert hidden not in names, "%s should be hidden from top-level help" % hidden + + def test_hidden_top_level_alias_still_runs(self, tmp_path): + from rsconnect.metadata import ServerStore + + store = ServerStore(base_dir=str(tmp_path)) + store.set("s1", "http://s1.local", api_key="key1") + runner = CliRunner() + with mock.patch("rsconnect.main.server_store", store): + result = runner.invoke(cli, ["remove", "--name", "s1"]) + assert result.exit_code == 0, result.output + assert "Removed" in result.output + class TestDeployGit(TestCase): """Tests for deploy git CLI command."""