module VersionSet = Set.Make(String); module Local = { type t = { name: string, fullPath: string, }; }; exception Version_not_found(string); module Remote = { type t = { name: string, baseURL: string, installed: bool, }; let skip = (~amount, str) => Str.last_chars(str, String.length(str) - amount); let parseSemver = version => version |> skip(~amount=1) |> Semver.of_string; let compare = (v1, v2) => switch (parseSemver(v1), parseSemver(v2)) { | (Some(v1), Some(v2)) => Semver.compare(v1, v2) | (None, _) | (_, None) => - Core.String.compare(v1, v2) }; let getInstalledVersionSet = () => Fs.readdir(Directories.nodeVersions) |> Result.fold(_ => [||], x => x) |> Array.fold_left( (acc, curr) => VersionSet.add(curr, acc), VersionSet.empty, ); let getRelativeLinksFromHTML = html => Soup.parse(html) |> Soup.select("pre a") |> Soup.to_list |> List.map(Soup.attribute("href")) |> Core.List.filter_map(~f=x => x); let downloadFileSuffix = ".tar.xz"; let getVersionFromFilename = filename => { let strings = filename |> String.split_on_char('-'); List.nth(strings, 1); }; }; let format = version => { let version = switch (Str.first_chars(version, 1) |> Int32.of_string) { | _ => "v" ++ version | exception _ => version }; version; }; let endsWith = (~suffix, str) => { let suffixLength = String.length(suffix); String.length(str) > suffixLength && Str.last_chars(str, suffixLength) == suffix; }; exception No_Download_For_System(System.NodeOS.t, System.NodeArch.t); let getFileToDownload = (~version as versionName, ~os, ~arch) => { let versionName = switch (Str.first_chars(versionName, 1) |> Int32.of_string) { | _ => "v" ++ versionName | exception _ => versionName }; let url = "https://nodejs.org/dist/" ++ versionName ++ "/"; let%lwt html = try%lwt (Http.makeRequest(url) |> Lwt.map(Http.body)) { | Http.Not_found(_) => Lwt.fail(Version_not_found(versionName)) }; let filenames = html |> Remote.getRelativeLinksFromHTML |> List.filter( endsWith( ~suffix= System.NodeOS.toString(os) ++ "-" ++ System.NodeArch.toString(arch) ++ Remote.downloadFileSuffix, ), ); switch (filenames |> List.hd) { | x => Lwt.return(url ++ x) | exception _ => Lwt.fail(No_Download_For_System(os, arch)) }; }; let getCurrentVersion = () => switch (Fs.realpath(Directories.currentVersion)) { | installationPath => let fullPath = Filename.dirname(installationPath); Some(Local.{fullPath, name: Core.Filename.basename(fullPath)}); | exception (Unix.Unix_error(_, _, _)) => None }; let getInstalledVersions = () => Fs.readdir(Directories.nodeVersions) |> Result.map(x => { Array.sort(Remote.compare, x); x; }) |> Result.map( Array.map(name => Local.{ name, fullPath: Filename.concat(Directories.nodeVersions, name), } ), ); let getRemoteVersions = () => { let%lwt bodyString = Http.makeRequest("https://nodejs.org/dist/") |> Lwt.map(Http.body); let versions = bodyString |> Remote.getRelativeLinksFromHTML; let installedVersions = Remote.getInstalledVersionSet(); versions |> Core.List.filter(~f=x => Str.last_chars(x, 1) == "/" && Str.first_chars(x, 1) != "." ) |> Core.List.map(~f=x => Str.first_chars(x, String.length(x) - 1)) |> List.sort(Remote.compare) |> List.map(name => Remote.{ name, installed: VersionSet.find_opt(name, installedVersions) != None, baseURL: "https://nodejs.org/dist/" ++ name ++ "/", } ) |> Lwt.return; };