Skip to content

Commit ffc1b66

Browse files
committed
Block --force with different integration, persist script type in init-options
- --force on install now rejects overwriting a different integration; users must use 'specify integration switch' instead - _update_init_options_for_integration() now accepts and persists script_type - Fix misleading test docstring for switch metadata test - Add test_force_blocked_with_different_integration
1 parent f5e9752 commit ffc1b66

2 files changed

Lines changed: 29 additions & 5 deletions

File tree

src/specify_cli/__init__.py

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1637,9 +1637,14 @@ def integration_install(
16371637
console.print("Use [cyan]--force[/cyan] to reinstall, or [cyan]specify integration switch <target>[/cyan] to change.")
16381638
raise typer.Exit(0)
16391639

1640-
if installed_key and installed_key != key and not force:
1640+
if installed_key and installed_key != key:
16411641
console.print(f"[red]Error:[/red] Integration '{installed_key}' is already installed.")
1642-
console.print(f"Use [cyan]specify integration switch {key}[/cyan] to switch, or [cyan]--force[/cyan] to overwrite.")
1642+
console.print(f"Use [cyan]specify integration switch {key}[/cyan] to switch integrations.")
1643+
if force:
1644+
console.print(
1645+
"[yellow]--force only supports reinstalling the currently installed integration "
1646+
"and cannot overwrite a different integration.[/yellow]"
1647+
)
16431648
raise typer.Exit(1)
16441649

16451650
selected_script = _resolve_script_type(project_root, script)
@@ -1662,7 +1667,7 @@ def integration_install(
16621667
)
16631668
manifest.save()
16641669
_write_integration_json(project_root, integration.key, selected_script)
1665-
_update_init_options_for_integration(project_root, integration)
1670+
_update_init_options_for_integration(project_root, integration, script_type=selected_script)
16661671

16671672
except Exception as e:
16681673
console.print(f"[red]Error:[/red] Failed to install integration: {e}")
@@ -1707,12 +1712,15 @@ def _parse_integration_options(integration: Any, raw_options: str) -> dict[str,
17071712
def _update_init_options_for_integration(
17081713
project_root: Path,
17091714
integration: Any,
1715+
script_type: str | None = None,
17101716
) -> None:
17111717
"""Update ``init-options.json`` to reflect *integration* as the active one."""
17121718
from .integrations.base import SkillsIntegration
17131719
opts = load_init_options(project_root)
17141720
opts["integration"] = integration.key
17151721
opts["ai"] = integration.key
1722+
if script_type:
1723+
opts["script"] = script_type
17161724
if isinstance(integration, SkillsIntegration):
17171725
opts["ai_skills"] = True
17181726
else:
@@ -1873,7 +1881,7 @@ def integration_switch(
18731881
)
18741882
manifest.save()
18751883
_write_integration_json(project_root, target_integration.key, selected_script)
1876-
_update_init_options_for_integration(project_root, target_integration)
1884+
_update_init_options_for_integration(project_root, target_integration, script_type=selected_script)
18771885

18781886
except Exception as e:
18791887
console.print(f"[red]Error:[/red] Failed to install integration '{target}': {e}")

tests/integrations/test_integration_subcommand.py

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,22 @@ def test_install_different_when_one_exists(self, tmp_path):
118118
assert result.exit_code != 0
119119
assert "already installed" in result.output
120120

121+
def test_force_blocked_with_different_integration(self, tmp_path):
122+
"""--force must not allow overwriting a different integration."""
123+
project = _init_project(tmp_path, "copilot")
124+
old_cwd = os.getcwd()
125+
try:
126+
os.chdir(project)
127+
result = runner.invoke(app, [
128+
"integration", "install", "claude", "--force",
129+
"--script", "sh",
130+
])
131+
finally:
132+
os.chdir(old_cwd)
133+
assert result.exit_code != 0
134+
assert "already installed" in result.output
135+
assert "cannot overwrite a different integration" in result.output
136+
121137
def test_install_into_bare_project(self, tmp_path):
122138
"""Install into a project with .specify/ but no integration."""
123139
project = tmp_path / "bare"
@@ -489,7 +505,7 @@ def test_init_options_cleared_on_no_manifest_uninstall(self, tmp_path):
489505

490506
class TestSwitchClearsMetadataAfterTeardown:
491507
def test_metadata_cleared_between_phases(self, tmp_path):
492-
"""If install phase fails during switch, metadata should not reference the removed integration."""
508+
"""After a successful switch, metadata should reference the new integration."""
493509
project = _init_project(tmp_path, "claude")
494510

495511
# Verify initial state

0 commit comments

Comments
 (0)