File Coverage

blib/lib/NVMPL/Installer.pm
Criterion Covered Total %
statement 68 82 82.9
branch 14 28 50.0
condition 2 5 40.0
subroutine 12 14 85.7
pod 0 1 0.0
total 96 130 73.8


line stmt bran cond sub pod time code
1             package NVMPL::Installer;
2 3     3   47797 use strict;
  3         5  
  3         117  
3 3     3   12 use warnings;
  3         14  
  3         235  
4 3     3   29 use feature 'say';
  3         6  
  3         352  
5 3     3   3971 use HTTP::Tiny;
  3         215911  
  3         246  
6 3     3   43 use File::Spec;
  3         7  
  3         113  
7 3     3   19 use File::Path qw(make_path);
  3         7  
  3         254  
8 3     3   2071 use Archive::Zip;
  3         219598  
  3         173  
9 3     3   67 use NVMPL::Config;
  3         7  
  3         129  
10 3     3   1370 use NVMPL::Utils qw(detect_platform);
  3         13  
  3         4180  
11              
12             # ---------------------------------------------------------
13             # Public entry point
14             # ---------------------------------------------------------
15              
16             sub install_version {
17 6     6 0 23384 my ($version) = @_;
18 6 50       32 unless ($version) {
19 0         0 say "Usage: nvm-pl install ";
20 0         0 exit 1;
21             }
22              
23 6         42 $version =~ s/^V//i;
24              
25 6 100       56 unless ($version =~ /^\d+\.\d+\.\d+$/) {
26 2         26 die "Invalid version format. Use X.Y.Z (e.g., 22.3.0)\n";
27             }
28            
29 4         16 my $vtag = "v$version";
30              
31 4         87 my $cfg = NVMPL::Config->load();
32 4         42 my $mirror = $cfg->{mirror_url};
33 4         11 my $install_dir = $cfg->{install_dir};
34 4         57 my $downloads = File::Spec->catdir($install_dir, 'downloads');
35 4         25 my $versions = File::Spec->catdir($install_dir, 'versions');
36              
37 4         41 my $platform = detect_platform();
38 4         27 my $os = _map_platform_to_node_os($platform);
39 4         30 my $arch = _detect_arch();
40 4 50       30 my $ext = $platform eq 'windows' ? 'zip' : 'tar.xz';
41              
42 4 100       1154 make_path($downloads) unless -d $downloads;
43 4 100       602 make_path($versions) unless -d $versions;
44              
45 4         27 my $filename = "node-$vtag-$os-$arch.$ext";
46 4         157 my $download_path = File::Spec->catfile($downloads, $filename);
47 4         44 my $target_dir = File::Spec->catdir($versions, $vtag);
48              
49 4 100       153 if (-d $target_dir) {
50 1         18 say "[nvm-pl] Node $vtag already installed.";
51 1         45 return;
52             }
53              
54 3         25 my $url = "$mirror/$vtag/$filename";
55 3         102 say "[nvm-pl] Fetching: $url";
56              
57 3 50       150 unless (-f $download_path) {
58 3         110 my $ua = HTTP::Tiny->new;
59 3         719 my $resp = _download_file($url, $download_path);
60             die "Download failed: $resp->{status} $resp->{reason}\n"
61 3 50       1066 unless $resp->{success};
62 3         126 say "[nvm-pl] Saved to $download_path";
63             } else {
64 0         0 say "[nvm-pl] Using cached file: $download_path";
65             }
66              
67 3         25 say "[nvm-pl] Extracting to $target_dir";
68 3         865 make_path($target_dir);
69              
70 3 50       36 if ($ext eq 'zip') {
71 0         0 my $zip = Archive::Zip->new();
72 0 0       0 $zip->read($download_path) == 0 or die "Failed to read zip\n";
73 0         0 $zip->extractTree('', "$target_dir/");
74             } else {
75 3         23 _should_extract_with_tar($download_path, $target_dir);
76             }
77              
78 3         215 say "[nvm-pl] Node $vtag installed successfully.";
79             }
80              
81             # ---------------------------------------------------------
82             # Helpers
83             # ---------------------------------------------------------
84              
85             sub _detect_arch {
86 4   0 4   34731 my $arch = `uname -m 2>/dev/null` || $ENV{PROCESSOR_ARCHITECTURE} || 'x64';
87 4         101 chomp $arch;
88 4 50       203 return 'x64' if $arch =~ /x86_64|amd64/i;
89 0 0       0 return 'arm64' if $arch =~ /arm64|aarch64/i;
90 0 0       0 return 'x86' if $arch =~ /i[3456]86/;
91 0         0 return $arch;
92             }
93              
94             sub _map_platform_to_node_os {
95 8     8   14598 my ($platform) = @_;
96 8         62 my %map = (
97             windows => 'win',
98             macos => 'darwin',
99             linux => 'linux',
100             );
101 8   66     82 return $map{$platform} || $platform;
102             }
103              
104             sub _should_extract_with_tar {
105 0     0     my ($download_path, $target_dir) = @_;
106 0 0         system("tar", "xf", $download_path, "-C", $target_dir, "--strip-components=1") == 0
107             or die "Extraction failed: $?";
108             }
109              
110             sub _download_file {
111 0     0     my ($url, $path) = @_;
112 0           my $ua = HTTP::Tiny->new;
113 0           return $ua->mirror($url, $path);
114             }
115              
116             1;