File Coverage

blib/lib/NVMPL/Uninstaller.pm
Criterion Covered Total %
statement 90 113 79.6
branch 23 40 57.5
condition 7 13 53.8
subroutine 12 14 85.7
pod 0 2 0.0
total 132 182 72.5


line stmt bran cond sub pod time code
1             package NVMPL::Uninstaller;
2 3     3   421654 use strict;
  3         16  
  3         121  
3 3     3   15 use warnings;
  3         6  
  3         245  
4 3     3   19 use feature 'say';
  3         5  
  3         411  
5 3     3   20 use File::Spec;
  3         6  
  3         119  
6 3     3   15 use File::Path qw(remove_tree);
  3         35  
  3         243  
7 3     3   598 use NVMPL::Config;
  3         7  
  3         94  
8 3     3   1818 use NVMPL::Switcher;
  3         10  
  3         6207  
9              
10             # ---------------------------------------------------------
11             # Public entry point
12             # ---------------------------------------------------------
13              
14             sub uninstall_version {
15 13     13 0 71680 my ($version) = @_;
16            
17 13 100       56 unless ($version) {
18 2         6 _exit_with_error("Usage: nvm-pl uninstall ");
19             }
20              
21 11         38 $version =~ s/^V//i;
22              
23             # Support both with and without 'v' prefix
24 11 50       43 my $vtag = $version =~ /^v/i ? $version : "v$version";
25              
26 11         66 my $cfg = NVMPL::Config->load();
27 11         101 my $install_dir = $cfg->{install_dir};
28 11         110 my $versions_dir = File::Spec->catdir($install_dir, 'versions');
29 11         53 my $target_dir = File::Spec->catdir($versions_dir, $vtag);
30              
31             # Check if version exists
32 11 100       310 unless (-d $target_dir) {
33 1         14 die "[nvm-pl] Node.js version $vtag is not installed.\n";
34             }
35              
36             # Check if this is the currently active version
37 10         38 my $current_version = NVMPL::Switcher::_get_current_version();
38 10 100 66     84 if ($current_version && $current_version eq $vtag) {
39 1         26 say "[nvm-pl] Warning: $vtag is currently in use.";
40 1         7 say " Switching to default version before uninstall...";
41            
42             # Try to switch to another installed version
43 1         41 my $alternative = _find_alternative_version($vtag);
44 1 50       4 if ($alternative) {
45 1         5 NVMPL::Switcher::use_version($alternative);
46             } else {
47 0         0 say "[nvm-pl] No other versions installed. You'll need to manually install another version.";
48            
49 0         0 my $current_link = File::Spec->catfile($versions_dir, 'current');
50 0 0       0 if (-l $current_link) {
51 0 0       0 unlink $current_link or warn "[nvm-pl] Could not remove broken 'current' symlink: $!";
52 0         0 say "[nvm-pl] Removed broken 'current' symlink.";
53             }
54             }
55             }
56              
57             # Confirm uninstall (optional - you might want to make this configurable)
58 10 100       35 unless (_confirm_uninstall($vtag)) {
59 1         16 say "[nvm-pl] Uninstall cancelled.";
60 1         11 return;
61             }
62              
63             # Remove the version directory
64 9         172 say "[nvm-pl] Removing Node.js $vtag...";
65 9         5851 my $success = remove_tree($target_dir, { error => \my $errors });
66              
67 9 50 33     83 if ($success && !@$errors) {
68 9         163 say "[nvm-pl] Successfully uninstalled Node.js $vtag";
69            
70             # Clean up any cached downloads for this version
71 9         32 _cleanup_cached_downloads($vtag);
72            
73             } else {
74 0         0 my $error_msg = join(', ', @$errors);
75 0         0 die "[nvm-pl] Failed to uninstall $vtag: $error_msg\n";
76             }
77             }
78              
79             # ---------------------------------------------------------
80             # Helper methods
81             # ---------------------------------------------------------
82              
83             sub _exit_with_error {
84 0     0   0 my ($message) = @_;
85 0         0 say $message;
86 0         0 exit 1;
87             }
88              
89             sub _find_alternative_version {
90 1     1   5 my ($exclude_version) = @_;
91            
92 1         6 my $cfg = NVMPL::Config->load();
93 1         18 my $versions_dir = File::Spec->catdir($cfg->{install_dir}, 'versions');
94            
95 1 50       46 opendir(my $dh, $versions_dir) or return undef;
96             my @versions = grep {
97 1 100 66     35 /^v\d+\.\d+\.\d+$/ && -d File::Spec->catdir($versions_dir, $_) && $_ ne $exclude_version
  5         96  
98             } readdir($dh);
99 1         14 closedir($dh);
100            
101             # Sort versions and return the highest one
102 1         9 @versions = sort { _compare_versions($b, $a) } @versions;
  1         4  
103 1 50       10 return $versions[0] if @versions;
104            
105 0         0 return undef;
106             }
107              
108             sub _compare_versions {
109 1     1   3 my ($a, $b) = @_;
110            
111             # Remove 'v' prefix for comparison
112 1         5 $a =~ s/^v//i;
113 1         4 $b =~ s/^v//i;
114            
115 1         6 my @a_parts = split(/\./, $a);
116 1         5 my @b_parts = split(/\./, $b);
117            
118 1         22 for my $i (0..2) {
119 1   50     8 my $a_val = $a_parts[$i] || 0;
120 1   50     4 my $b_val = $b_parts[$i] || 0;
121 1 50       10 return $a_val <=> $b_val if $a_val != $b_val;
122             }
123            
124 0         0 return 0;
125             }
126              
127             sub _confirm_uninstall {
128 0     0   0 my ($version) = @_;
129            
130             # You might want to make this configurable via NVMPL::Config
131             # For now, we'll always ask for confirmation
132            
133 0         0 print "[nvm-pl] Are you sure you want to uninstall Node.js $version? [y/N] ";
134 0         0 my $response = ;
135 0         0 chomp $response;
136            
137 0         0 return $response =~ /^y(es)?$/i;
138             }
139              
140             sub _cleanup_cached_downloads {
141 9     9   24 my ($version) = @_;
142            
143 9         74 my $cfg = NVMPL::Config->load();
144 9         208 my $downloads_dir = File::Spec->catdir($cfg->{install_dir}, 'downloads');
145            
146             # Look for download files matching this version
147 9 50       387 opendir(my $dh, $downloads_dir) or return;
148 9         238 my @matching_files = grep { /node-$version-/ } readdir($dh);
  20         275  
149 9         170 closedir($dh);
150            
151 9         102 foreach my $file (@matching_files) {
152 2         31 my $file_path = File::Spec->catfile($downloads_dir, $file);
153 2 50       47 if (-f $file_path) {
154 2 50       209 unlink $file_path or warn "[nvm-pl] Warning: Could not remove cached file $file_path: $!\n";
155 2         44 say "[nvm-pl] Removed cached download: $file";
156             }
157             }
158             }
159              
160             # ---------------------------------------------------------
161             # Batch uninstall - optional feature
162             # ---------------------------------------------------------
163              
164             sub uninstall_multiple {
165 1     1 0 11082 my (@versions) = @_;
166            
167 1 50       5 unless (@versions) {
168 0         0 say "Usage: nvm-pl uninstall [version2 ...]";
169 0         0 exit 1;
170             }
171            
172 1         3 my @successful = ();
173 1         2 my @failed = ();
174            
175 1         3 foreach my $version (@versions) {
176 3         7 eval {
177 3         14 uninstall_version($version);
178 3         10 push @successful, $version;
179             };
180 3 50       13 if ($@) {
181 0         0 warn "[nvm-pl] Failed to uninstall $version: $@";
182 0         0 push @failed, $version;
183             }
184             }
185            
186 1 50       5 if (@successful) {
187 1         14 say "[nvm-pl] Successfully uninstalled: " . join(', ', @successful);
188             }
189            
190 1 50       9 if (@failed) {
191 0           say "[nvm-pl] Failed to uninstall: " . join(', ', @failed);
192 0           exit 1;
193             }
194             }
195              
196             1;