From 1535f7397d666b0dae38eb798a796d40f3c2f53a Mon Sep 17 00:00:00 2001 From: Pascal Date: Thu, 14 May 2026 00:58:49 +0200 Subject: [PATCH] fix(integration): clarify multi-install guidance --- src/specify_cli/__init__.py | 20 ++++++++-- .../test_integration_subcommand.py | 37 +++++++++++++++++-- 2 files changed, 50 insertions(+), 7 deletions(-) diff --git a/src/specify_cli/__init__.py b/src/specify_cli/__init__.py index d4e8632215..c167886862 100644 --- a/src/specify_cli/__init__.py +++ b/src/specify_cli/__init__.py @@ -1696,10 +1696,18 @@ def integration_install( if key in installed_keys: console.print(f"[yellow]Integration '{key}' is already installed.[/yellow]") + if default_key == key: + console.print("It is already the default integration.") + else: + console.print( + f"To make it the default integration, run " + f"[cyan]specify integration use {key}[/cyan]." + ) console.print( - f"Run [cyan]specify integration upgrade {key}[/cyan] to reinstall managed files, " - f"or [cyan]specify integration uninstall {key}[/cyan] first." + f"To refresh its managed files or options, run " + f"[cyan]specify integration upgrade {key}[/cyan]." ) + console.print("No files were changed.") raise typer.Exit(0) if installed_keys and not force: @@ -1719,8 +1727,12 @@ def integration_install( "integrations are declared multi-install safe." ) console.print( - f"Run [cyan]specify integration switch {key}[/cyan] to replace the default " - f"integration, or retry with [cyan]--force[/cyan] to opt in." + f"To replace the default integration, run " + f"[cyan]specify integration switch {key}[/cyan]." + ) + console.print( + f"To install '{key}' alongside the existing integrations anyway, " + "retry the same install command with [cyan]--force[/cyan]." ) raise typer.Exit(1) diff --git a/tests/integrations/test_integration_subcommand.py b/tests/integrations/test_integration_subcommand.py index e36067b3cf..abff9a5ee1 100644 --- a/tests/integrations/test_integration_subcommand.py +++ b/tests/integrations/test_integration_subcommand.py @@ -163,7 +163,30 @@ def test_install_already_installed(self, tmp_path): assert "already installed" in result.output normalized = " ".join(result.output.split()) assert "specify integration upgrade copilot" in normalized - assert "specify integration uninstall copilot" in normalized + assert "already the default integration" in normalized + assert "No files were changed" in normalized + assert "specify integration uninstall copilot" not in normalized + + def test_install_already_installed_non_default_guides_use(self, tmp_path): + project = _init_project(tmp_path, "claude") + old_cwd = os.getcwd() + try: + os.chdir(project) + install = runner.invoke(app, [ + "integration", "install", "codex", + "--script", "sh", + ], catch_exceptions=False) + assert install.exit_code == 0, install.output + + result = runner.invoke(app, ["integration", "install", "codex"]) + finally: + os.chdir(old_cwd) + assert result.exit_code == 0 + normalized = " ".join(result.output.split()) + assert "already installed" in normalized + assert "specify integration use codex" in normalized + assert "specify integration upgrade codex" in normalized + assert "specify integration uninstall codex" not in normalized def test_install_different_when_one_exists(self, tmp_path): project = _init_project(tmp_path, "copilot") @@ -176,7 +199,11 @@ def test_install_different_when_one_exists(self, tmp_path): assert result.exit_code != 0 assert "Installed integrations: copilot" in result.output assert "Default integration: copilot" in result.output - assert "--force" in result.output + normalized = " ".join(result.output.split()) + assert "To replace the default integration" in normalized + assert "specify integration switch claude" in normalized + assert "To install 'claude' alongside" in normalized + assert "retry the same install command with --force" in normalized def test_install_multi_safe_integration(self, tmp_path): project = _init_project(tmp_path, "claude") @@ -261,7 +288,11 @@ def test_install_multi_unsafe_requires_force(self, tmp_path): assert result.exit_code != 0 assert "Installed integrations: copilot" in result.output assert "multi-install safe" in result.output - assert "--force" in result.output + normalized = " ".join(result.output.split()) + assert "To replace the default integration" in normalized + assert "specify integration switch claude" in normalized + assert "To install 'claude' alongside" in normalized + assert "retry the same install command with --force" in normalized def test_install_multi_unsafe_allowed_with_force(self, tmp_path): project = _init_project(tmp_path, "copilot")