Uninstall a workflow.
(
workflow_id: str = typer.Argument(..., help="Workflow ID to uninstall"),
)
| 819 | |
| 820 | @workflow_app.command("remove") |
| 821 | def workflow_remove( |
| 822 | workflow_id: str = typer.Argument(..., help="Workflow ID to uninstall"), |
| 823 | ): |
| 824 | """Uninstall a workflow.""" |
| 825 | from .catalog import WorkflowRegistry |
| 826 | |
| 827 | project_root = _require_specify_project() |
| 828 | workflows_dir = project_root / ".specify" / "workflows" |
| 829 | _validate_workflow_id_or_exit(workflow_id) |
| 830 | |
| 831 | registry = WorkflowRegistry(project_root) |
| 832 | |
| 833 | if not registry.is_installed(workflow_id): |
| 834 | console.print(f"[red]Error:[/red] Workflow '{workflow_id}' is not installed") |
| 835 | raise typer.Exit(1) |
| 836 | |
| 837 | # Remove workflow files |
| 838 | workflow_dir_unresolved = workflows_dir / workflow_id |
| 839 | safe_id = _escape_markup(workflow_id) |
| 840 | if workflow_dir_unresolved.is_symlink(): |
| 841 | console.print( |
| 842 | f"[red]Error:[/red] Refusing to remove symlinked " |
| 843 | f".specify/workflows/{safe_id}" |
| 844 | ) |
| 845 | raise typer.Exit(1) |
| 846 | |
| 847 | workflow_dir = workflow_dir_unresolved.resolve() |
| 848 | try: |
| 849 | rel_parts = workflow_dir.relative_to(workflows_dir.resolve()).parts |
| 850 | except ValueError: |
| 851 | console.print( |
| 852 | f"[red]Error:[/red] Invalid workflow ID: {_escape_markup(repr(workflow_id))}" |
| 853 | ) |
| 854 | raise typer.Exit(1) |
| 855 | if rel_parts != (workflow_id,): |
| 856 | console.print( |
| 857 | f"[red]Error:[/red] Invalid workflow ID: {_escape_markup(repr(workflow_id))}" |
| 858 | ) |
| 859 | raise typer.Exit(1) |
| 860 | |
| 861 | if workflow_dir.exists() and not workflow_dir.is_dir(): |
| 862 | console.print( |
| 863 | f"[red]Error:[/red] .specify/workflows/{safe_id} exists but is not a directory" |
| 864 | ) |
| 865 | raise typer.Exit(1) |
| 866 | |
| 867 | if workflow_dir.exists(): |
| 868 | import shutil |
| 869 | try: |
| 870 | shutil.rmtree(workflow_dir) |
| 871 | except OSError as exc: |
| 872 | console.print( |
| 873 | f"[red]Error:[/red] Failed to remove workflow directory {workflow_dir}: {exc}" |
| 874 | ) |
| 875 | raise typer.Exit(1) |
| 876 | |
| 877 | registry.remove(workflow_id) |
| 878 | console.print(f"[green]✓[/green] Workflow '{workflow_id}' removed") |
no test coverage detected