1- import type { CommandExecutor } from "@effect/platform/CommandExecutor"
1+ import type * as CommandExecutor from "@effect/platform/CommandExecutor"
22import type { PlatformError } from "@effect/platform/Error"
33import * as FileSystem from "@effect/platform/FileSystem"
44import * as Path from "@effect/platform/Path"
55import { Effect } from "effect"
66import { deriveRepoPathParts } from "../core/domain.js"
7- import { runDockerComposeDown } from "../shell/docker.js"
8- import type { DockerCommandError } from "../shell/errors.js"
7+ import { runCommandWithExitCodes } from "../shell/command-runner.js"
8+ import { runDockerComposeDownVolumes } from "../shell/docker.js"
9+ import { CommandFailedError , type DockerCommandError } from "../shell/errors.js"
910import { renderError } from "./errors.js"
1011import { defaultProjectsRoot } from "./menu-helpers.js"
1112import type { ProjectItem } from "./projects-core.js"
@@ -25,19 +26,52 @@ const isWithinProjectsRoot = (path: Path.Path, root: string, target: string): bo
2526 return true
2627}
2728
29+ const removeContainerByName = (
30+ cwd : string ,
31+ containerName : string
32+ ) : Effect . Effect < void , never , CommandExecutor . CommandExecutor > =>
33+ runCommandWithExitCodes (
34+ {
35+ cwd,
36+ command : "docker" ,
37+ args : [ "rm" , "-f" , containerName ]
38+ } ,
39+ [ 0 ] ,
40+ ( exitCode ) => new CommandFailedError ( { command : `docker rm -f ${ containerName } ` , exitCode } )
41+ ) . pipe (
42+ Effect . matchEffect ( {
43+ onFailure : ( error ) =>
44+ Effect . logWarning ( `docker rm -f fallback failed for ${ containerName } : ${ renderError ( error ) } ` ) ,
45+ onSuccess : ( ) => Effect . log ( `Removed container: ${ containerName } ` )
46+ } ) ,
47+ Effect . asVoid
48+ )
49+
50+ const removeContainersFallback = (
51+ item : ProjectItem
52+ ) : Effect . Effect < void , never , CommandExecutor . CommandExecutor > =>
53+ Effect . gen ( function * ( _ ) {
54+ yield * _ ( removeContainerByName ( item . projectDir , item . containerName ) )
55+ yield * _ ( removeContainerByName ( item . projectDir , `${ item . containerName } -browser` ) )
56+ } )
57+
2858// CHANGE: delete a docker-git project directory (state) selected in the TUI
2959// WHY: allow removing unwanted projects without rewriting git history (just delete the folder)
3060// QUOTE(ТЗ): "Сделай возможность так же удалять мусорный для меня контейнер... Не нужно чистить гит историю. Пусть просто папку с ним удалит"
3161// REF: user-request-2026-02-09-delete-project
3262// SOURCE: n/a
33- // FORMAT THEOREM: forall p: delete(p) -> !exists(projectDir(p))
63+ // FORMAT THEOREM: forall p: delete(p) -> !exists(projectDir(p)) && !container_exists(p)
3464// PURITY: SHELL
3565// EFFECT: Effect<void, PlatformError | DockerCommandError, FileSystem | Path | CommandExecutor>
3666// INVARIANT: never deletes paths outside the projects root
3767// COMPLEXITY: O(docker + fs)
3868export const deleteDockerGitProject = (
3969 item : ProjectItem
40- ) : Effect . Effect < void , PlatformError | DockerCommandError , FileSystem . FileSystem | Path . Path | CommandExecutor > =>
70+ ) : Effect . Effect <
71+ void ,
72+ PlatformError | DockerCommandError ,
73+ FileSystem . FileSystem | Path . Path | CommandExecutor . CommandExecutor
74+ > =>
4175 Effect . gen ( function * ( _ ) {
4276 const fs = yield * _ ( FileSystem . FileSystem )
4377 const path = yield * _ ( Path . Path )
@@ -56,14 +90,17 @@ export const deleteDockerGitProject = (
5690 return
5791 }
5892
59- // Best-effort: stop the container if possible before removing the compose dir .
93+ // Best-effort: remove compose containers and volumes before deleting the project folder .
6094 yield * _ (
61- runDockerComposeDown ( targetDir ) . pipe (
62- Effect . catchTag (
63- "DockerCommandError" ,
64- ( error : DockerCommandError ) =>
65- Effect . logWarning ( `docker compose down failed before delete: ${ renderError ( error ) } ` )
66- )
95+ runDockerComposeDownVolumes ( targetDir ) . pipe (
96+ Effect . matchEffect ( {
97+ onFailure : ( error ) =>
98+ Effect . gen ( function * ( _ ) {
99+ yield * _ ( Effect . logWarning ( `docker compose down -v failed before delete: ${ renderError ( error ) } ` ) )
100+ yield * _ ( removeContainersFallback ( item ) )
101+ } ) ,
102+ onSuccess : ( ) => Effect . void
103+ } )
67104 )
68105 )
69106
0 commit comments