001package org.jdrupes.builder.ext.nodejs; 002 003import java.io.File; 004import java.io.IOException; 005import java.io.InputStream; 006import java.net.URL; 007import java.nio.file.Files; 008import java.nio.file.Path; 009import java.nio.file.StandardCopyOption; 010import java.util.Locale; 011import java.util.zip.ZipEntry; 012import java.util.zip.ZipInputStream; 013import org.apache.commons.compress.archivers.tar.TarArchiveEntry; 014import org.apache.commons.compress.archivers.tar.TarArchiveInputStream; 015import org.apache.commons.compress.compressors.gzip.GzipCompressorInputStream; 016 017public class NodeDownloader { 018 019 private final Path baseDir; 020 021 public NodeDownloader(Path baseDir) { 022 this.baseDir = baseDir; 023 } 024 025 public void ensureInstalled(String version) throws Exception { 026 Path installDir = getInstallDir(version); 027 if (Files.exists(installDir)) { 028 return; // already installed 029 } 030 031 Files.createDirectories(baseDir); 032 033 Platform platform = detectPlatform(); 034 String fileName = buildFileName(version, platform); 035 String downloadUrl 036 = "https://nodejs.org/dist/v" + version + "/" + fileName; 037 038 Path archive = baseDir.resolve(fileName); 039 040 download(downloadUrl, archive); 041 042 if (fileName.endsWith(".zip")) { 043 unzip(archive, baseDir); 044 } else if (fileName.endsWith(".tar.gz")) { 045 untarGz(archive, baseDir); 046 } 047 048 Files.deleteIfExists(archive); 049 } 050 051 public File getNpmExecutable(String version) { 052 Platform platform = detectPlatform(); 053 Path installDir = getInstallDir(version); 054 055 if (platform.isWindows) { 056 return installDir.resolve("npm.cmd").toFile(); 057 } else { 058 return installDir.resolve("bin/npm").toFile(); 059 } 060 } 061 062 private Path getInstallDir(String version) { 063 Platform p = detectPlatform(); 064 String folderName = "node-v" + version + "-" + p.platformName; 065 return baseDir.resolve(folderName); 066 } 067 068 private void download(String url, Path target) throws IOException { 069 try (InputStream in = new URL(url).openStream()) { 070 Files.copy(in, target, StandardCopyOption.REPLACE_EXISTING); 071 } 072 } 073 074 private void unzip(Path zipFile, Path targetDir) throws IOException { 075 try (ZipInputStream zis 076 = new ZipInputStream(Files.newInputStream(zipFile))) { 077 ZipEntry entry; 078 while ((entry = zis.getNextEntry()) != null) { 079 Path newPath = targetDir.resolve(entry.getName()); 080 if (entry.isDirectory()) { 081 Files.createDirectories(newPath); 082 } else { 083 Files.createDirectories(newPath.getParent()); 084 Files.copy(zis, newPath, 085 StandardCopyOption.REPLACE_EXISTING); 086 } 087 } 088 } 089 } 090 091 private void untarGz(Path tarGz, Path targetDir) throws IOException { 092 try (InputStream fi = Files.newInputStream(tarGz); 093 InputStream gzi = new GzipCompressorInputStream(fi); 094 TarArchiveInputStream tais = new TarArchiveInputStream(gzi)) { 095 096 TarArchiveEntry entry; 097 while ((entry = tais.getNextTarEntry()) != null) { 098 Path newPath = targetDir.resolve(entry.getName()); 099 if (entry.isDirectory()) { 100 Files.createDirectories(newPath); 101 } else { 102 Files.createDirectories(newPath.getParent()); 103 Files.copy(tais, newPath, 104 StandardCopyOption.REPLACE_EXISTING); 105 } 106 } 107 } 108 } 109 110 private Platform detectPlatform() { 111 String os = System.getProperty("os.name").toLowerCase(Locale.ROOT); 112 String arch 113 = System.getProperty("os.arch").contains("64") ? "x64" : "x86"; 114 115 if (os.contains("win")) { 116 return new Platform("win-" + arch, true, ".zip"); 117 } else if (os.contains("mac")) { 118 return new Platform("darwin-" + arch, false, ".tar.gz"); 119 } else if (os.contains("nux")) { 120 return new Platform("linux-" + arch, false, ".tar.gz"); 121 } 122 123 throw new RuntimeException("Unsupported OS: " + os); 124 } 125 126 private String buildFileName(String version, Platform p) { 127 return "node-v" + version + "-" + p.platformName + p.extension; 128 } 129 130 private static class Platform { 131 final String platformName; 132 final boolean isWindows; 133 final String extension; 134 135 Platform(String platformName, boolean isWindows, String extension) { 136 this.platformName = platformName; 137 this.isWindows = isWindows; 138 this.extension = extension; 139 } 140 } 141 142}