You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

262 lines
6.6 KiB

module VersionSet = Set.Make(String);
let lwtIgnore = lwt => Lwt.catch(() => lwt, _ => Lwt.return());
module Local = {
type t = {
name: string,
fullPath: string,
aliases: list(string),
};
let toDirectory = name => Filename.concat(Directories.nodeVersions, name);
};
exception Version_not_found(string);
exception Already_installed(string);
module Aliases = {
module VersionAliasMap = Map.Make(String);
type t = {
name: string,
versionName: string,
fullPath: string,
};
let toDirectory = name => Filename.concat(Directories.aliases, name);
let getAll = () => {
let%lwt aliases =
try%lwt (Fs.readdir(Directories.aliases)) {
| _ => Lwt.return([])
};
aliases
|> List.map(alias => {
let fullPath = Filename.concat(Directories.aliases, alias);
{
name: alias,
fullPath,
versionName:
Filename.concat(Directories.aliases, alias)
|> Fs.realpath
|> Filename.basename,
};
})
|> Lwt.return;
};
let byVersion = () => {
let%lwt aliases = getAll();
aliases
|> List.fold_left(
(map, curr) => {
let value =
switch (VersionAliasMap.find_opt(curr.versionName, map)) {
| None => [curr.name]
| Some(arr) => [curr.name, ...arr]
};
VersionAliasMap.add(curr.versionName, value, map);
},
VersionAliasMap.empty,
)
|> Lwt.return;
};
let set = (~alias, ~versionPath) => {
let aliasPath = alias |> toDirectory;
let%lwt _ = System.mkdirp(Directories.aliases);
let%lwt _ = Lwt_unix.unlink(aliasPath) |> lwtIgnore;
let%lwt _ = Lwt_unix.symlink(versionPath, aliasPath);
Lwt.return();
};
};
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 = () =>
Lwt.(
catch(() => Fs.readdir(Directories.nodeVersions), _ => return([]))
>|= List.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)
|> List.map(x => {
let parts = String.split_on_char('/', x) |> List.rev;
switch (parts) {
| ["", x, ...xs] => x ++ "/"
| [x, ...xs] => 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 =
Printf.sprintf("%s%s/", Config.FNM_NODE_DIST_MIRROR.get(), 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), aliases: []},
);
| exception (Unix.Unix_error(_, _, _)) => None
};
let getInstalledVersions = () => {
let%lwt versions =
Fs.readdir(Directories.nodeVersions)
|> Lwt.map(List.sort(Remote.compare))
and aliases = Aliases.byVersion();
versions
|> List.map(name =>
Local.{
name,
fullPath: Filename.concat(Directories.nodeVersions, name),
aliases: Opt.(Aliases.VersionAliasMap.find_opt(name, aliases) or []),
}
)
|> Lwt.return;
};
let getRemoteVersions = () => {
let%lwt bodyString =
Config.FNM_NODE_DIST_MIRROR.get()
|> Http.makeRequest
|> Lwt.map(Http.body);
let versions = bodyString |> Remote.getRelativeLinksFromHTML;
let%lwt 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:
Printf.sprintf("%s%s/", Config.FNM_NODE_DIST_MIRROR.get(), name),
}
)
|> Lwt.return;
};
type t =
| Alias(string)
| Local(string);
let parse = version => {
let formattedVersion = format(version);
let aliasPath = Aliases.toDirectory(version);
let versionPath = Local.toDirectory(formattedVersion);
let%lwt aliasExists = Lwt_unix.file_exists(aliasPath)
and versionExists = Lwt_unix.file_exists(versionPath);
switch (versionExists, aliasExists) {
| (true, _) => Some(Local(formattedVersion)) |> Lwt.return
| (_, true) => Some(Alias(version)) |> Lwt.return
| (false, false) => Lwt.return_none
};
};
let throwIfInstalled = versionName => {
let%lwt installedVersions =
try%lwt (getInstalledVersions()) {
| _ => Lwt.return([])
};
let isAlreadyInstalled =
installedVersions |> List.exists(x => Local.(x.name == versionName));
if (isAlreadyInstalled) {
Lwt.fail(Already_installed(versionName));
} else {
Lwt.return();
};
};