Uninstall an integration, safely preserving modified files.
(
key: str = typer.Argument(None, help="Integration key to uninstall (default: current integration)"),
force: bool = typer.Option(False, "--force", help="Remove files even if modified"),
)
| 196 | |
| 197 | @integration_app.command("uninstall") |
| 198 | def integration_uninstall( |
| 199 | key: str = typer.Argument(None, help="Integration key to uninstall (default: current integration)"), |
| 200 | force: bool = typer.Option(False, "--force", help="Remove files even if modified"), |
| 201 | ): |
| 202 | """Uninstall an integration, safely preserving modified files.""" |
| 203 | from . import get_integration |
| 204 | from .manifest import IntegrationManifest |
| 205 | from .. import _require_specify_project |
| 206 | |
| 207 | project_root = _require_specify_project() |
| 208 | current = _read_integration_json(project_root) |
| 209 | default_key = _default_integration_key(current) |
| 210 | installed_keys = _installed_integration_keys(current) |
| 211 | |
| 212 | if key is None: |
| 213 | if not default_key: |
| 214 | console.print("[yellow]No integration is currently installed.[/yellow]") |
| 215 | raise typer.Exit(0) |
| 216 | key = default_key |
| 217 | |
| 218 | if key not in installed_keys: |
| 219 | console.print(f"[red]Error:[/red] Integration '{key}' is not installed.") |
| 220 | raise typer.Exit(1) |
| 221 | |
| 222 | integration = get_integration(key) |
| 223 | |
| 224 | manifest_path = project_root / ".specify" / "integrations" / f"{key}.manifest.json" |
| 225 | if not manifest_path.exists(): |
| 226 | console.print(f"[yellow]No manifest found for integration '{key}'. Nothing to uninstall.[/yellow]") |
| 227 | remaining = [installed for installed in installed_keys if installed != key] |
| 228 | new_default = default_key if default_key != key else (remaining[0] if remaining else None) |
| 229 | if remaining: |
| 230 | if default_key == key and new_default and (new_integration := get_integration(new_default)): |
| 231 | raw_options, parsed_options = _resolve_integration_options( |
| 232 | new_integration, current, new_default, None |
| 233 | ) |
| 234 | _set_default_integration_or_exit( |
| 235 | project_root, |
| 236 | current, |
| 237 | new_default, |
| 238 | new_integration, |
| 239 | remaining, |
| 240 | raw_options=raw_options, |
| 241 | parsed_options=parsed_options, |
| 242 | ) |
| 243 | else: |
| 244 | _write_integration_json( |
| 245 | project_root, new_default, remaining, _integration_settings(current) |
| 246 | ) |
| 247 | else: |
| 248 | _remove_integration_json(project_root) |
| 249 | if default_key == key: |
| 250 | _clear_init_options_for_integration(project_root, key) |
| 251 | raise typer.Exit(0) |
| 252 | |
| 253 | try: |
| 254 | manifest = IntegrationManifest.load(key, project_root) |
| 255 | except _MANIFEST_READ_ERRORS as exc: |
nothing calls this directly
no test coverage detected