Browse Source

Add support for `lts/*` (#146)

* support lts/*

* Remove redundant line

* Add test

* Slight refactor
remotes/origin/add-simple-redirecting-site
Gal Schlezinger 5 years ago committed by GitHub
parent
commit
65c6d569af
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 2
      executable/Env.re
  2. 29
      executable/Install.re
  3. 4
      executable/ListLocal.re
  4. 22
      executable/Use.re
  5. 1
      feature_tests/latest-lts/.nvmrc
  6. 10
      feature_tests/latest-lts/run.sh
  7. 2
      feature_tests/run.sh
  8. 2
      library/Dotfiles.re
  9. 6
      library/Fs.re
  10. 48
      library/Path.re
  11. 12
      library/Result.re
  12. 2
      library/System.re
  13. 34
      library/VersionListing.re
  14. 29
      library/VersionListingLts.re
  15. 21
      library/Versions.re
  16. 4
      library/dune
  17. 6
      package.json

2
executable/Env.re

@ -1,7 +1,7 @@
open Fnm; open Fnm;
let symlinkExists = path => let symlinkExists = path =>
try%lwt (Lwt_unix.lstat(path) |> Lwt.map(_ => true)) { try%lwt(Lwt_unix.lstat(path) |> Lwt.map(_ => true)) {
| _ => Lwt.return(false) | _ => Lwt.return(false)
}; };

29
executable/Install.re

@ -71,6 +71,18 @@ let main = (~version as versionName) => {
let versionName = Versions.format(versionName); let versionName = Versions.format(versionName);
let%lwt versionName =
switch (versionName) {
| "latest-*" =>
switch%lwt (VersionListingLts.getLatest()) {
| Error(err) =>
raise(VersionListingLts.Problem_with_finding_latest_lts(err))
| Ok({VersionListingLts.lts, _}) =>
Printf.sprintf("latest-%s", lts) |> Lwt.return
}
| _ => Lwt.return(versionName)
};
Logger.debug( Logger.debug(
<Pastel> <Pastel>
"Looking for node " "Looking for node "
@ -115,7 +127,7 @@ let main = (~version as versionName) => {
}; };
let run = (~version) => let run = (~version) =>
try%lwt (main(~version)) { try%lwt(main(~version)) {
| Versions.No_Download_For_System(os, arch) => | Versions.No_Download_For_System(os, arch) =>
Logger.error( Logger.error(
<Pastel> <Pastel>
@ -137,4 +149,19 @@ let run = (~version) =>
</Pastel>, </Pastel>,
); );
exit(1); exit(1);
| VersionListingLts.Problem_with_finding_latest_lts(
VersionListingLts.Cant_find_latest_lts,
) =>
Logger.error(<Pastel color=Pastel.Red> "Can't find latest LTS" </Pastel>);
exit(1);
| VersionListingLts.Problem_with_finding_latest_lts(
VersionListingLts.Cant_parse_remote_version_listing(reason),
) =>
Logger.error(
<Pastel color=Pastel.Red>
"Can't parse json of the response:\n"
reason
</Pastel>,
);
exit(1);
}; };

4
executable/ListLocal.re

@ -6,7 +6,7 @@ let main = () =>
Versions.Local.( Versions.Local.(
{ {
let%lwt versions = let%lwt versions =
try%lwt (Versions.getInstalledVersions()) { try%lwt(Versions.getInstalledVersions()) {
| _ => Lwt.fail(Cant_read_local_versions) | _ => Lwt.fail(Cant_read_local_versions)
} }
and currentVersion = Versions.getCurrentVersion(); and currentVersion = Versions.getCurrentVersion();
@ -36,7 +36,7 @@ let main = () =>
); );
let run = () => let run = () =>
try%lwt (main()) { try%lwt(main()) {
| Cant_read_local_versions => | Cant_read_local_versions =>
Console.log("No versions installed!") |> Lwt.return Console.log("No versions installed!") |> Lwt.return
}; };

22
executable/Use.re

@ -19,12 +19,26 @@ let error = (~quiet, arg) =>
Logger.error(arg); Logger.error(arg);
}; };
let getVersion = version => {
let%lwt parsed = Versions.parse(version);
let%lwt resultWithLts =
switch (parsed) {
| Ok(x) => Lwt.return_ok(x)
| Error("latest-*") =>
switch%lwt (VersionListingLts.getLatest()) {
| Error(_) => Lwt.return_error(Version_Not_Installed(version))
| Ok({VersionListingLts.lts, _}) =>
Versions.Alias("latest-" ++ lts) |> Lwt.return_ok
}
| _ => Version_Not_Installed(version) |> Lwt.return_error
};
resultWithLts |> Result.fold(Lwt.fail, Lwt.return);
};
let switchVersion = (~version, ~quiet) => { let switchVersion = (~version, ~quiet) => {
open Lwt.Infix;
let info = info(~quiet); let info = info(~quiet);
let debug = debug(~quiet); let debug = debug(~quiet);
let%lwt parsedVersion = let%lwt parsedVersion = getVersion(version);
Versions.parse(version) >>= Opt.toLwt(Version_Not_Installed(version));
let%lwt versionPath = let%lwt versionPath =
switch (parsedVersion) { switch (parsedVersion) {
@ -102,7 +116,7 @@ let rec askIfInstall = (~version, ~quiet, retry) => {
}; };
let rec run = (~version, ~quiet) => let rec run = (~version, ~quiet) =>
try%lwt (main(~version, ~quiet)) { try%lwt(main(~version, ~quiet)) {
| Version_Not_Installed(versionString) => | Version_Not_Installed(versionString) =>
error( error(
~quiet, ~quiet,

1
feature_tests/latest-lts/.nvmrc

@ -0,0 +1 @@
lts/*

10
feature_tests/latest-lts/run.sh

@ -0,0 +1,10 @@
#!/bin/bash
set -e
eval "$(fnm env --multi)"
fnm install
fnm use
fnm ls | grep latest-

2
feature_tests/run.sh

@ -19,7 +19,7 @@ rm -rf $TEMP_DIR_BASE
mkdir $TEMP_DIR_BASE $TEMP_BINARY_PATH mkdir $TEMP_DIR_BASE $TEMP_BINARY_PATH
cp $BINARY $TEMP_BINARY_PATH/fnm cp $BINARY $TEMP_BINARY_PATH/fnm
for test_file in $DIRECTORY/*/run.sh; do for test_file in $DIRECTORY/latest-lts/run.sh; do
rm -rf $TEMP_FNM_DIR rm -rf $TEMP_FNM_DIR
echo "Running test in $test_file" echo "Running test in $test_file"

2
library/Dotfiles.re

@ -2,7 +2,7 @@ exception Version_Not_Provided;
exception Conflicting_Dotfiles_Found(string, string); exception Conflicting_Dotfiles_Found(string, string);
let versionString = fileName => { let versionString = fileName => {
try%lwt ( try%lwt(
Lwt_io.lines_of_file(fileName) Lwt_io.lines_of_file(fileName)
|> Lwt_stream.to_list |> Lwt_stream.to_list
|> Lwt.map(List.hd) |> Lwt.map(List.hd)

6
library/Fs.re

@ -15,7 +15,7 @@ let readdir = dir => {
}; };
let%lwt items = let%lwt items =
try%lwt (iterate()) { try%lwt(iterate()) {
| End_of_file => Lwt.return(items^) | End_of_file => Lwt.return(items^)
}; };
@ -33,13 +33,13 @@ let writeFile = (path, contents) => {
}; };
let exists = path => { let exists = path => {
try%lwt (Lwt_unix.file_exists(path)) { try%lwt(Lwt_unix.file_exists(path)) {
| Unix.Unix_error(_, _, _) => Lwt.return(false) | Unix.Unix_error(_, _, _) => Lwt.return(false)
}; };
}; };
let readlink = path => let readlink = path =>
try%lwt (Lwt_unix.readlink(path) |> Lwt.map(x => Ok(x))) { try%lwt(Lwt_unix.readlink(path) |> Lwt.map(x => Ok(x))) {
| err => Lwt.return_error(err) | err => Lwt.return_error(err)
}; };

48
library/Path.re

@ -146,9 +146,9 @@ let lex = s => {
prevEsc.contents = ch === '\\' && !prevEsc.contents; prevEsc.contents = ch === '\\' && !prevEsc.contents;
}; };
let rev = let rev =
j.contents === len - 1 ? j.contents === len - 1
revTokens.contents : ? revTokens.contents
[ : [
makeToken(String.sub(s, j.contents + 1, len - 1 - j.contents)), makeToken(String.sub(s, j.contents + 1, len - 1 - j.contents)),
...revTokens.contents, ...revTokens.contents,
]; ];
@ -256,10 +256,14 @@ let relativeExn = s =>
* Relates two positive integers to zero and eachother. * Relates two positive integers to zero and eachother.
*/ */
type ord = type ord =
| /** 0 === i === j */ Zeros | /** 0 === i === j */
| /** 0 === i < j */ ZeroPositive Zeros
| /** i > 0 === j */ PositiveZero | /** 0 === i < j */
| /** 0 < i && 0 < j */ Positives; ZeroPositive
| /** i > 0 === j */
PositiveZero
| /** 0 < i && 0 < j */
Positives;
/** /**
* Using `ord` allows us to retain exhaustiveness pattern matching checks that * Using `ord` allows us to retain exhaustiveness pattern matching checks that
@ -268,8 +272,8 @@ type ord =
* it isn't inferred to be polymorphic. * it isn't inferred to be polymorphic.
*/ */
let ord = (i: int, j: int) => let ord = (i: int, j: int) =>
i === 0 && j === 0 ? i === 0 && j === 0
Zeros : i === 0 ? ZeroPositive : j === 0 ? PositiveZero : Positives; ? Zeros : i === 0 ? ZeroPositive : j === 0 ? PositiveZero : Positives;
let rec repeat = (soFar, i, s) => let rec repeat = (soFar, i, s) =>
i === 0 ? soFar : repeat(soFar ++ s, i - 1, s); i === 0 ? soFar : repeat(soFar ++ s, i - 1, s);
@ -334,14 +338,14 @@ let relativizeExn: type k. (~source: t(k), ~dest: t(k)) => t(relative) =
| (Some(_), None) => raiseDriveMismatch(source, dest) | (Some(_), None) => raiseDriveMismatch(source, dest)
| (None, Some(_)) => raiseDriveMismatch(source, dest) | (None, Some(_)) => raiseDriveMismatch(source, dest)
| (Some(d1), Some(d2)) => | (Some(d1), Some(d2)) =>
String.compare(d1, d2) !== 0 ? String.compare(d1, d2) !== 0
raiseDriveMismatch(source, dest) : ? raiseDriveMismatch(source, dest)
relativizeDepth((0, List.rev(s1)), (0, List.rev(s2))) : relativizeDepth((0, List.rev(s1)), (0, List.rev(s2)))
} }
| ((Rel(w1, r1), s1), (Rel(w2, r2), s2)) => | ((Rel(w1, r1), s1), (Rel(w2, r2), s2)) =>
w1 === w2 ? w1 === w2
relativizeDepth((r1, List.rev(s1)), (r2, List.rev(s2))) : ? relativizeDepth((r1, List.rev(s1)), (r2, List.rev(s2)))
raiseDriveMismatch(source, dest) : raiseDriveMismatch(source, dest)
}; };
(Rel(Any, depth), List.rev(segs)); (Rel(Any, depth), List.rev(segs));
}; };
@ -349,7 +353,7 @@ let relativizeExn: type k. (~source: t(k), ~dest: t(k)) => t(relative) =
let relativize: let relativize:
type k. (~source: t(k), ~dest: t(k)) => result(t(relative), exn) = type k. (~source: t(k), ~dest: t(k)) => result(t(relative), exn) =
(~source, ~dest) => (~source, ~dest) =>
try (Ok(relativizeExn(~source, ~dest))) { try(Ok(relativizeExn(~source, ~dest))) {
| Invalid_argument(_) as e => Error(e) | Invalid_argument(_) as e => Error(e)
}; };
@ -413,9 +417,9 @@ let rec join: type k1 k2. (t(k1), t(k2)) => t(k1) =
switch (p1, p2) { switch (p1, p2) {
| ((Rel(w, r1), []), (Rel(Any, r2), s2)) => (Rel(w, r1 + r2), s2) | ((Rel(w, r1), []), (Rel(Any, r2), s2)) => (Rel(w, r1 + r2), s2)
| ((Rel(w, r1), [s1hd, ...s1tl] as s1), (Rel(Any, r2), s2)) => | ((Rel(w, r1), [s1hd, ...s1tl] as s1), (Rel(Any, r2), s2)) =>
r2 > 0 ? r2 > 0
join((Rel(w, r1), s1tl), (Rel(Any, r2 - 1), s2)) : ? join((Rel(w, r1), s1tl), (Rel(Any, r2 - 1), s2))
(Rel(w, r1), List.append(s2, s1)) : (Rel(w, r1), List.append(s2, s1))
| ((b1, s1), (Rel(Home, r2), s2)) => | ((b1, s1), (Rel(Home, r2), s2)) =>
join((b1, [homeChar, ...List.append(s2, s1)]), (Rel(Any, r2), s2)) join((b1, [homeChar, ...List.append(s2, s1)]), (Rel(Any, r2), s2))
| ((b1, s1), (Abs(Some(ll)), s2)) => ( | ((b1, s1), (Abs(Some(ll)), s2)) => (
@ -425,9 +429,9 @@ let rec join: type k1 k2. (t(k1), t(k2)) => t(k1) =
| ((b1, s1), (Abs(None), s2)) => (b1, List.append(s2, s1)) | ((b1, s1), (Abs(None), s2)) => (b1, List.append(s2, s1))
| ((Abs(_) as d, []), (Rel(Any, r2), s2)) => (d, s2) | ((Abs(_) as d, []), (Rel(Any, r2), s2)) => (d, s2)
| ((Abs(_) as d, [s1hd, ...s1tl] as s1), (Rel(Any, r2), s2)) => | ((Abs(_) as d, [s1hd, ...s1tl] as s1), (Rel(Any, r2), s2)) =>
r2 > 0 ? r2 > 0
join((d, s1tl), (Rel(Any, r2 - 1), s2)) : ? join((d, s1tl), (Rel(Any, r2 - 1), s2))
(d, List.append(s2, s1)) : (d, List.append(s2, s1))
}; };
let dirName: type k1. t(k1) => t(k1) = let dirName: type k1. t(k1) => t(k1) =

12
library/Result.re

@ -25,6 +25,18 @@ let bind = (fn, res) =>
| Error(_) as e => e | Error(_) as e => e
}; };
let bind_err = (fn, res) =>
switch (res) {
| Ok(_) as o => o
| Error(x) => fn(x)
};
let bind_err_lwt = (fn, res) =>
switch (res) {
| Ok(o) => Lwt.return_ok(o)
| Error(x) => fn(x)
};
let fold = (error, ok, res) => let fold = (error, ok, res) =>
switch (res) { switch (res) {
| Ok(x) => ok(x) | Ok(x) => ok(x)

2
library/System.re

@ -70,7 +70,7 @@ module NodeArch = {
switch (Sys.os_type) { switch (Sys.os_type) {
| "Unix" => | "Unix" =>
let%lwt result = unix_exec("uname", ~args=[|"-a"|]); let%lwt result = unix_exec("uname", ~args=[|"-a"|]);
try (result |> findArches |> Lwt.return) { try(result |> findArches |> Lwt.return) {
| _ => Lwt.fail_with("Error getting unix information") | _ => Lwt.fail_with("Error getting unix information")
}; };
| _ => Lwt.return(Other) | _ => Lwt.return(Other)

34
library/VersionListing.re

@ -0,0 +1,34 @@
type item = {
version: string,
date: string,
files: list(string),
lts: option(string),
};
let item_of_yojson = (json: Yojson.Safe.t) => {
open Yojson.Safe.Util;
let version = json |> member("version") |> to_string;
let files = json |> member("files") |> to_list |> List.map(to_string);
let date = json |> member("date") |> to_string;
let lts =
Base.Option.try_with(() => {
json |> member("lts") |> to_string |> String.lowercase_ascii
});
Ok({version, date, files, lts});
};
[@deriving of_yojson({strict: false})]
type t = list(item);
let parseVersionListing = body => {
Base.Result.try_with(() => Yojson.Safe.from_string(body))
|> Base.Result.map_error(~f=Printexc.to_string)
|> Base.Result.bind(~f=x => of_yojson(x));
};
let fromHttp = () => {
let url =
Config.FNM_NODE_DIST_MIRROR.get() |> Printf.sprintf("%s/index.json");
let%lwt {Http.body, _} = Http.makeRequest(url);
parseVersionListing(body) |> Lwt.return;
};

29
library/VersionListingLts.re

@ -0,0 +1,29 @@
type item = {
version: string,
lts: string,
};
let item_to_lts = (item: VersionListing.item) => {
item.lts |> Base.Option.map(~f=lts => {lts, version: item.version});
};
type get_latest_lts_errors =
| Cant_parse_remote_version_listing(string)
| Cant_find_latest_lts;
exception Problem_with_finding_latest_lts(get_latest_lts_errors);
let getLatest = () => {
let%lwt versions = VersionListing.fromHttp();
versions
|> Base.Result.map_error(~f=err => Cant_parse_remote_version_listing(err))
|> Base.Result.bind(~f=parsed => {
parsed
|> Base.List.filter_map(~f=item_to_lts)
|> Base.List.max_elt(~compare=(a, b) =>
Versions.compare(a.version, b.version)
)
|> Base.Result.of_option(~error=Cant_find_latest_lts)
})
|> Lwt.return;
};

21
library/Versions.re

@ -79,7 +79,7 @@ module Aliases = {
let getAll = () => { let getAll = () => {
let%lwt aliases = let%lwt aliases =
try%lwt (Fs.readdir(Directories.aliases)) { try%lwt(Fs.readdir(Directories.aliases)) {
| _ => Lwt.return([]) | _ => Lwt.return([])
}; };
aliases aliases
@ -293,7 +293,7 @@ let getExactFileToDownload = (~version as versionName, ~os, ~arch) => {
Printf.sprintf("%s%s/", Config.FNM_NODE_DIST_MIRROR.get(), versionName); Printf.sprintf("%s%s/", Config.FNM_NODE_DIST_MIRROR.get(), versionName);
let%lwt html = let%lwt html =
try%lwt (Http.makeRequest(url) |> Lwt.map(Http.body)) { try%lwt(Http.makeRequest(url) |> Lwt.map(Http.body)) {
| Http.Not_found(_) => Lwt.fail(Version_not_found(versionName)) | Http.Not_found(_) => Lwt.fail(Version_not_found(versionName))
}; };
@ -319,7 +319,7 @@ let getExactFileToDownload = (~version as versionName, ~os, ~arch) => {
}; };
let getFileToDownload = (~version, ~os, ~arch) => let getFileToDownload = (~version, ~os, ~arch) =>
try%lwt (getExactFileToDownload(~version, ~os, ~arch)) { try%lwt(getExactFileToDownload(~version, ~os, ~arch)) {
| Version_not_found(_) as e => | Version_not_found(_) as e =>
switch%lwt (getRemoteLatestVersionByPrefix(version)) { switch%lwt (getRemoteLatestVersionByPrefix(version)) {
| None => Lwt.fail(e) | None => Lwt.fail(e)
@ -337,7 +337,7 @@ let parse = version => {
let formattedVersion = format(version); let formattedVersion = format(version);
if (formattedVersion == Local.systemVersion.name) { if (formattedVersion == Local.systemVersion.name) {
Lwt.return_some(System); Lwt.return_ok(System);
} else { } else {
let%lwt aliasExists = Aliases.toDirectory(version) |> Fs.exists let%lwt aliasExists = Aliases.toDirectory(version) |> Fs.exists
and aliasExistsOnFormatted = and aliasExistsOnFormatted =
@ -352,19 +352,18 @@ let parse = version => {
aliasExistsOnFormatted, aliasExistsOnFormatted,
versionByPrefixPath, versionByPrefixPath,
) { ) {
| (true, _, _, _) => Some(Local(formattedVersion)) |> Lwt.return | (true, _, _, _) => Local(formattedVersion) |> Lwt.return_ok
| (_, true, _, _) => Some(Alias(version)) |> Lwt.return | (_, true, _, _) => Alias(version) |> Lwt.return_ok
| (_, _, true, _) => Some(Alias(formattedVersion)) |> Lwt.return | (_, _, true, _) => Alias(formattedVersion) |> Lwt.return_ok
| (_, false, false, Some(version)) => | (_, false, false, Some(version)) => Local(version) |> Lwt.return_ok
Some(Local(version)) |> Lwt.return | (false, false, false, None) => Lwt.return_error(formattedVersion)
| (false, false, false, None) => Lwt.return_none
}; };
}; };
}; };
let isInstalled = versionName => { let isInstalled = versionName => {
let%lwt installedVersions = let%lwt installedVersions =
try%lwt (getInstalledVersions()) { try%lwt(getInstalledVersions()) {
| _ => Lwt.return([]) | _ => Lwt.return([])
}; };
installedVersions installedVersions

4
library/dune

@ -7,6 +7,6 @@
(name Fnm) (name Fnm)
; Other libraries list this name in their package.json 'require' field to use this library. ; Other libraries list this name in their package.json 'require' field to use this library.
(public_name fnm.lib) (public_name fnm.lib)
(libraries pastel.lib str base lwt ssl lwt_ssl lambdasoup cohttp cohttp-lwt cohttp-lwt-unix console.lib ) (libraries pastel.lib str base lwt ssl lwt_ssl lambdasoup cohttp cohttp-lwt cohttp-lwt-unix console.lib ppx_deriving_yojson.runtime )
(preprocess ( pps lwt_ppx ppx_let ppx_deriving.show ppx_deriving.eq ppx_deriving.make ppx_deriving.ord )) ; From package.json preprocess field (preprocess ( pps lwt_ppx ppx_let ppx_deriving.show ppx_deriving.eq ppx_deriving.make ppx_deriving.ord ppx_deriving_yojson )) ; From package.json preprocess field
) )

6
package.json

@ -31,7 +31,8 @@
"ppx_deriving.show", "ppx_deriving.show",
"ppx_deriving.eq", "ppx_deriving.eq",
"ppx_deriving.make", "ppx_deriving.make",
"ppx_deriving.ord" "ppx_deriving.ord",
"ppx_deriving_yojson"
], ],
"require": [ "require": [
"pastel.lib", "pastel.lib",
@ -44,7 +45,8 @@
"cohttp", "cohttp",
"cohttp-lwt", "cohttp-lwt",
"cohttp-lwt-unix", "cohttp-lwt-unix",
"console.lib" "console.lib",
"ppx_deriving_yojson.runtime"
], ],
"name": "fnm.lib", "name": "fnm.lib",
"namespace": "Fnm" "namespace": "Fnm"

Loading…
Cancel
Save