Browse Source

Aliases and multishell support (#30)

* Use variants instead of a boolean

* Add infrastructure for multishell and aliases: Aliases are required because I want to have a default node version on startup

* create alias command

* fmt

* Added aliases (Fixes #29) and opt-in multishell support (Fixes #19)

* Better docs

* update snapshot

* Some/Fail => Some/None

* add lint-staged

* use refmt and all of prettier are grouped

* System.readdir => Fs.readdir (now uses Lwt)

* use lstat for file_exists: check if symlink exists instead of actual linked file. also, initialize the Random seed on Env

* Remove fish set options that were added in trial and error

* Bootstrap script

* add bootstrap documentation
remotes/origin/add-simple-redirecting-site
Gal Schlezinger 6 years ago committed by GitHub
parent
commit
05e27c6ea3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 11
      .ci/bootstrap
  2. 5
      .ci/pre-commit-hook
  3. 16
      README.md
  4. 4063
      esy.lock/index.json
  5. 4
      esy.lock/opam/ppxlib.0.5.0/opam
  6. 12
      esy.lock/opam/uutf.1.0.2/opam
  7. 6
      esy.lock/opam/yojson.1.6.0/opam
  8. 30
      executable/Alias.re
  9. 50
      executable/Env.re
  10. 64
      executable/FnmApp.re
  11. 17
      executable/ListLocal.re
  12. 49
      executable/Use.re
  13. 1
      feature_tests/fish/run.fish
  14. 31
      feature_tests/multishell/run.sh
  15. 4
      library/Compression.re
  16. 6
      library/Directories.re
  17. 27
      library/Fs.re
  18. 4
      library/Http.re
  19. 6
      library/Opt.re
  20. 6
      library/System.re
  21. 148
      library/Versions.re
  22. 59
      package.json
  23. 1
      test/__snapshots__/Smoke_test.4d362c3c.0.snapshot

11
.ci/bootstrap

@ -0,0 +1,11 @@ @@ -0,0 +1,11 @@
#!/bin/bash
GIT_ROOT=$(git rev-parse --show-toplevel)
if [ "$GIT_ROOT" == "" ]; then
echo "Git root cannot be empty"
exit 1
fi
rm -f $GIT_ROOT/.git/hooks/pre-commit &> /dev/null
ln -s $GIT_ROOT/.ci/pre-commit-hook $GIT_ROOT/.git/hooks/pre-commit

5
.ci/pre-commit-hook

@ -0,0 +1,5 @@ @@ -0,0 +1,5 @@
#!/bin/bash
set -e
npx lint-staged

16
README.md

@ -11,6 +11,7 @@ @@ -11,6 +11,7 @@
</div>
## Features
:sparkles: Single file, easy installation
:rocket: Built with speed in mind
@ -29,9 +30,9 @@ curl https://raw.githubusercontent.com/Schniz/fnm/master/.ci/install.sh | bash @@ -29,9 +30,9 @@ curl https://raw.githubusercontent.com/Schniz/fnm/master/.ci/install.sh | bash
### Manually
* Download the [latest release binary](https://github.com/Schniz/fnm/releases) for your system
* Make it available globally on `$PATH`
* Add the following line to your `.bashrc`/`.zshrc` file:
- Download the [latest release binary](https://github.com/Schniz/fnm/releases) for your system
- Make it available globally on `$PATH`
- Add the following line to your `.bashrc`/`.zshrc` file:
```bash
eval `fnm env`
@ -63,11 +64,15 @@ Lists the installed Node versions. @@ -63,11 +64,15 @@ Lists the installed Node versions.
Lists the Node versions available to download remotely.
### `fnm env [--fish]`
### `fnm env [--multi] [--fish]`
Prints the required shell commands in order to configure your shell, Bash compliant by default.
Prints the required shell commands in order to configure your shell, Bash compliant by default. Provide `--fish` to output the Fish-compliant version.
- Providing `--multi` will output the multishell support, allowing a different current Node version per shell
- Providing `--fish` will output the Fish-compliant version.
## Future Plans
- [ ] Feature: make versions complete the latest: `10` would infer the latest minor and patch versions of node 10. `10.1` would infer the latest patch version of node 10.1
- [ ] Feature: `fnm use --install`, `fnm use --quiet`
- [ ] Feature: `fnm install lts`?
@ -88,6 +93,7 @@ PRs welcome :tada: @@ -88,6 +93,7 @@ PRs welcome :tada:
npm install -g esy
git clone https://github.com/Schniz/fnm.git
esy install
esy bootstrap
esy build
```

4063
esy.lock/index.json

File diff suppressed because it is too large Load Diff

4
esy.lock/opam/ppxlib.0.5.0/opam

@ -15,12 +15,12 @@ run-test: [ @@ -15,12 +15,12 @@ run-test: [
]
depends: [
"ocaml" {>= "4.04.1"}
"base" {>= "v0.11.0"}
"base" {>= "v0.11.0" & < "v0.12"}
"dune" {build}
"ocaml-compiler-libs" {>= "v0.11.0"}
"ocaml-migrate-parsetree" {>= "1.0.9"}
"ppx_derivers" {>= "1.0"}
"stdio" {>= "v0.11.0"}
"stdio" {>= "v0.11.0" & < "v0.12"}
"ocamlfind" {with-test}
]
synopsis: "Base library and tools for ppx rewriters"

12
esy.lock/opam/uutf.1.0.1/opam → esy.lock/opam/uutf.1.0.2/opam

@ -20,8 +20,9 @@ build: [[ @@ -20,8 +20,9 @@ build: [[
"ocaml" "pkg/pkg.ml" "build"
"--pinned" "%{pinned}%"
"--with-cmdliner" "%{cmdliner:installed}%" ]]
synopsis: "Non-blocking streaming Unicode codec for OCaml"
description: """
synopsis: """Non-blocking streaming Unicode codec for OCaml"""
description: """\
Uutf is a non-blocking streaming codec to decode and encode the UTF-8,
UTF-16, UTF-16LE and UTF-16BE encoding schemes. It can efficiently
work character by character without blocking on IO. Decoders perform
@ -31,8 +32,9 @@ Functions are also provided to fold over the characters of UTF encoded @@ -31,8 +32,9 @@ Functions are also provided to fold over the characters of UTF encoded
OCaml string values and to directly encode characters in OCaml
Buffer.t values.
Uutf has no dependency and is distributed under the ISC license."""
Uutf has no dependency and is distributed under the ISC license.
"""
url {
src: "http://erratique.ch/software/uutf/releases/uutf-1.0.1.tbz"
checksum: "md5=b8535f974027357094c5cdb4bf03a21b"
archive: "http://erratique.ch/software/uutf/releases/uutf-1.0.2.tbz"
checksum: "a7c542405a39630c689a82bd7ef2292c"
}

6
esy.lock/opam/yojson.1.5.0/opam → esy.lock/opam/yojson.1.6.0/opam

@ -9,12 +9,14 @@ build: [ @@ -9,12 +9,14 @@ build: [
["dune" "subst"] {pinned}
["dune" "build" "-p" name "-j" jobs]
]
run-test: [["dune" "runtest" "-p" name "-j" jobs]]
depends: [
"ocaml" {>= "4.02.3"}
"dune" {build}
"cppo" {build}
"easy-format"
"biniou" {>= "1.2.0"}
"alcotest" {with-test & >= "0.8.5"}
]
synopsis:
"Yojson is an optimized parsing and printing library for the JSON format"
@ -31,6 +33,6 @@ The program atdgen can be used to derive OCaml-JSON serializers and @@ -31,6 +33,6 @@ The program atdgen can be used to derive OCaml-JSON serializers and
deserializers from type definitions."""
url {
src:
"https://github.com/ocaml-community/yojson/releases/download/1.5.0/yojson-1.5.0.tbz"
checksum: "md5=d80de1bacdde292af42f7c78b323da7b"
"https://github.com/ocaml-community/yojson/releases/download/1.6.0/yojson-1.6.0.tbz"
checksum: "md5=8ca16557d3068253cc375452af3bde96"
}

30
executable/Alias.re

@ -0,0 +1,30 @@ @@ -0,0 +1,30 @@
open Fnm;
let run = (~name, ~version) => {
let version = Versions.format(version);
let versionPath = Filename.concat(Directories.nodeVersions, version);
let%lwt versionInstalled = Lwt_unix.file_exists(versionPath);
if (!versionInstalled) {
Console.error(
<Pastel color=Pastel.Red>
"Can't find a version installed in "
versionPath
</Pastel>,
);
exit(1);
};
Console.log(
<Pastel>
"Aliasing "
<Pastel color=Pastel.Cyan> name </Pastel>
" to "
<Pastel color=Pastel.Cyan> version </Pastel>
</Pastel>,
);
let%lwt () = Versions.Aliases.set(~alias=name, ~versionPath);
Lwt.return();
};

50
executable/Env.re

@ -1,14 +1,50 @@ @@ -1,14 +1,50 @@
open Fnm;
let run = isFishShell => {
if (isFishShell) {
Console.log(
Printf.sprintf("set PATH %s/bin $PATH", Directories.currentVersion),
let symlinkExists = path => {
try%lwt (Lwt_unix.lstat(path) |> Lwt.map(_ => true)) {
| _ => Lwt.return(false)
};
};
let rec makeTemporarySymlink = () => {
let suggestedName =
Filename.concat(
Filename.get_temp_dir_name(),
"fnm-shell-"
++ (Random.int32(9999999 |> Int32.of_int) |> Int32.to_string),
);
let%lwt exists = symlinkExists(suggestedName);
if (exists) {
let%lwt suggestedName = makeTemporarySymlink();
Lwt.return(suggestedName);
} else {
Console.log(
Printf.sprintf("export PATH=%s/bin:$PATH", Directories.currentVersion),
);
let%lwt _ =
Lwt_unix.symlink(
Filename.concat(Directories.defaultVersion, "installation"),
suggestedName,
);
Lwt.return(suggestedName);
};
};
let run = (~shell, ~multishell) => {
open Lwt;
Random.self_init();
let%lwt path =
multishell
? makeTemporarySymlink() : Lwt.return(Directories.globalCurrentVersion);
switch (shell) {
| System.Shell.Bash =>
Printf.sprintf("export PATH=%s/bin:$PATH", path) |> Console.log;
Printf.sprintf("export FNM_MULTISHELL_PATH=%s", path) |> Console.log;
| System.Shell.Fish =>
Printf.sprintf("set PATH %s/bin $PATH;", path) |> Console.log;
Printf.sprintf("set FNM_MULTISHELL_PATH %s;", path) |> Console.log;
};
Lwt.return();

64
executable/FnmApp.re

@ -1,11 +1,18 @@ @@ -1,11 +1,18 @@
let version = Fnm.Fnm__Package.version;
module Commands = {
let use = version => Lwt_main.run(Use.run(version));
let use = (version, quiet) => Lwt_main.run(Use.run(~version, ~quiet));
let alias = (version, name) => Lwt_main.run(Alias.run(~name, ~version));
let listRemote = () => Lwt_main.run(ListRemote.run());
let listLocal = () => Lwt_main.run(ListLocal.run());
let install = version => Lwt_main.run(Install.run(~version));
let env = isFishShell => Lwt_main.run(Env.run(isFishShell));
let env = (isFishShell, isMultishell) =>
Lwt_main.run(
Env.run(
~shell=Fnm.System.Shell.(isFishShell ? Fish : Bash),
~multishell=isMultishell,
),
);
};
open Cmdliner;
@ -71,6 +78,11 @@ let use = { @@ -71,6 +78,11 @@ let use = {
let doc = "Switch to another installed node version";
let man = [];
let quiet = {
let doc = "Don't print stuff";
Arg.(value & flag & info(["quiet"], ~doc));
};
let selectedVersion = {
let doc = "Switch to version $(docv).\nLeave empty to look for value from `.nvmrc`";
Arg.(
@ -79,11 +91,45 @@ let use = { @@ -79,11 +91,45 @@ let use = {
};
(
Term.(const(Commands.use) $ selectedVersion),
Term.(const(Commands.use) $ selectedVersion $ quiet),
Term.info("use", ~version, ~doc, ~exits=Term.default_exits, ~man),
);
};
let alias = {
let doc = "Alias a version";
let sdocs = Manpage.s_common_options;
let man = help_secs;
let selectedVersion = {
let doc = "The version to be aliased";
Arg.(
required
& pos(0, some(string), None)
& info([], ~docv="VERSION", ~doc)
);
};
let aliasName = {
let doc = "The alias name";
Arg.(
required & pos(1, some(string), None) & info([], ~docv="NAME", ~doc)
);
};
(
Term.(const(Commands.alias) $ selectedVersion $ aliasName),
Term.info(
"alias",
~version,
~doc,
~exits=Term.default_exits,
~man,
~sdocs,
),
);
};
let env = {
let doc = "Show env configurations";
let sdocs = Manpage.s_common_options;
@ -94,8 +140,13 @@ let env = { @@ -94,8 +140,13 @@ let env = {
Arg.(value & flag & info(["fish"], ~doc));
};
let isMultishell = {
let doc = "Allow different Node versions for each shell";
Arg.(value & flag & info(["multi"], ~doc));
};
(
Term.(const(Commands.env) $ isFishShell),
Term.(const(Commands.env) $ isFishShell $ isMultishell),
Term.info("env", ~version, ~doc, ~exits=Term.default_exits, ~man, ~sdocs),
);
};
@ -119,5 +170,8 @@ let defaultCmd = { @@ -119,5 +170,8 @@ let defaultCmd = {
};
let _ =
Term.eval_choice(defaultCmd, [install, use, listLocal, listRemote, env])
Term.eval_choice(
defaultCmd,
[install, use, alias, listLocal, listRemote, env],
)
|> Term.exit;

17
executable/ListLocal.re

@ -6,22 +6,29 @@ let main = () => @@ -6,22 +6,29 @@ let main = () =>
Versions.Local.(
{
let%lwt versions =
Versions.getInstalledVersions()
|> Result.mapError(_ => Cant_read_local_versions)
|> Result.toLwtErr;
try%lwt (Versions.getInstalledVersions()) {
| _ => Lwt.fail(Cant_read_local_versions)
};
let currentVersion = Versions.getCurrentVersion();
Console.log("The following versions are installed:");
versions
|> Array.iter(version => {
|> List.iter(version => {
let color =
switch (currentVersion) {
| None => None
| Some(x) when x.name == version.name => Some(Pastel.Cyan)
| Some(_) => None
};
Console.log(<Pastel ?color> "* " {version.name} </Pastel>);
let aliases =
List.length(version.aliases) === 0
? ""
: Printf.sprintf(
" (%s)",
version.aliases |> String.concat(", "),
);
Console.log(<Pastel ?color> "* " {version.name} aliases </Pastel>);
});
Lwt.return();

49
executable/Use.re

@ -4,18 +4,27 @@ let lwtIgnore = lwt => Lwt.catch(() => lwt, _ => Lwt.return()); @@ -4,18 +4,27 @@ let lwtIgnore = lwt => Lwt.catch(() => lwt, _ => Lwt.return());
exception Version_Not_Installed(string);
let switchVersion = version => {
let versionDir = Filename.concat(Directories.nodeVersions, version);
let log = (~quiet, arg) =>
if (!quiet) {
Console.log(arg);
};
let%lwt _ =
if%lwt (Lwt_unix.file_exists(versionDir) |> Lwt.map(x => !x)) {
Lwt.fail(Version_Not_Installed(version));
let switchVersion = (~version, ~quiet) => {
open Lwt;
let log = log(~quiet);
let%lwt parsedVersion =
Versions.parse(version) >>= Opt.toLwt(Version_Not_Installed(version));
let%lwt versionPath =
switch (parsedVersion) {
| Local(version) => Versions.Local.toDirectory(version) |> Lwt.return
| Alias(alias) => Versions.Aliases.toDirectory(alias) |> Lwt.return
};
let destination = Filename.concat(versionDir, "installation");
let destination = Filename.concat(versionPath, "installation");
let source = Directories.currentVersion;
Console.log(
log(
<Pastel>
"Linking "
<Pastel color=Pastel.Cyan> source </Pastel>
@ -25,28 +34,37 @@ let switchVersion = version => { @@ -25,28 +34,37 @@ let switchVersion = version => {
);
let%lwt _ = Lwt_unix.unlink(Directories.currentVersion) |> lwtIgnore;
let%lwt _ = Lwt_unix.symlink(destination, Directories.currentVersion);
let%lwt _ = Lwt_unix.symlink(destination, Directories.currentVersion)
and defaultAliasExists = Lwt_unix.file_exists(Directories.defaultVersion);
let%lwt _ =
if (!defaultAliasExists) {
Versions.Aliases.set(~alias="default", ~versionPath=destination);
} else {
Lwt.return();
};
Console.log(
log(
<Pastel> "Using " <Pastel color=Pastel.Cyan> version </Pastel> </Pastel>,
);
Lwt.return();
};
let main = (~version as providedVersion) => {
let main = (~version as providedVersion, ~quiet) => {
let%lwt version =
switch (providedVersion) {
| Some(version) => Lwt.return(version)
| None => Nvmrc.getVersion()
};
switchVersion(Versions.format(version));
switchVersion(~version, ~quiet);
};
let run = version =>
try%lwt (main(~version)) {
let run = (~version, ~quiet) =>
try%lwt (main(~version, ~quiet)) {
| Version_Not_Installed(version) =>
Console.log(
log(
~quiet,
<Pastel color=Pastel.Red>
"The following version is not installed: "
version
@ -54,7 +72,8 @@ let run = version => @@ -54,7 +72,8 @@ let run = version =>
)
|> Lwt.return
| Nvmrc.Version_Not_Provided =>
Console.log(
log(
~quiet,
<Pastel color=Pastel.Red>
"No .nvmrc was found in the current directory. Please provide a version number."
</Pastel>,

1
feature_tests/fish/run.fish

@ -1,6 +1,7 @@ @@ -1,6 +1,7 @@
#!/usr/bin/env fish
eval (fnm env --fish)
fnm install v8.11.3
fnm use v8.11.3

31
feature_tests/multishell/run.sh

@ -0,0 +1,31 @@ @@ -0,0 +1,31 @@
#!/bin/bash
set -e
eval $(fnm env)
fnm install v8.11.3
fnm install v11.9.0
fnm use v8.11.3
bash -c '
set -e
eval $(fnm env --multi)
fnm use v11.9.0
echo "> verifying version v11.9.0 for child bash"
if [ "$(node -v)" == "v11.9.0" ]; then
echo "Okay!"
else
echo "Node version should be v11.9.0 in the bash fork"
exit 1
fi
'
echo "> verifying version v8.11.3 for parent bash"
if [ "$(node -v)" == "v8.11.3" ]; then
echo "Okay!"
else
echo "Node version should be v8.11.3 in the base bash"
exit 1
fi

4
library/Compression.re

@ -6,8 +6,8 @@ let extractFile = (~into as destination, filepath) => { @@ -6,8 +6,8 @@ let extractFile = (~into as destination, filepath) => {
~args=[|"-xvf", filepath, "--directory", destination|],
~stderr=`Dev_null,
);
let%lwt files = Fs.readdir(destination) |> Result.toLwt;
let filename = files[0];
let%lwt files = Fs.readdir(destination);
let filename = List.hd(files);
Lwt_unix.rename(
Filename.concat(destination, filename),
Filename.concat(destination, "installation"),

6
library/Directories.re

@ -9,5 +9,9 @@ let sfwRoot = @@ -9,5 +9,9 @@ let sfwRoot =
}
);
let nodeVersions = Filename.concat(sfwRoot, "node-versions");
let currentVersion = Filename.concat(sfwRoot, "current");
let globalCurrentVersion = Filename.concat(sfwRoot, "current");
let currentVersion =
Opt.(Sys.getenv_opt("FNM_MULTISHELL_PATH") or globalCurrentVersion);
let downloads = Filename.concat(sfwRoot, "downloads");
let aliases = Filename.concat(sfwRoot, "aliases");
let defaultVersion = Filename.concat(aliases, "default");

27
library/Fs.re

@ -1,11 +1,30 @@ @@ -1,11 +1,30 @@
open Core;
let readdir = dir =>
switch (Sys.readdir(dir)) {
| x => Ok(x)
| exception (Sys_error(error)) => Error(error)
let readdir = dir => {
let items = ref([]);
let%lwt dir = Lwt_unix.opendir(dir);
let iterate = () => {
let%lwt _ =
while%lwt (true) {
let%lwt value = Lwt_unix.readdir(dir);
if (value.[0] != '.') {
items := [value, ...items^];
};
Lwt.return();
};
Lwt.return([]);
};
let%lwt items =
try%lwt (iterate()) {
| End_of_file => Lwt.return(items^)
};
let%lwt _ = Lwt_unix.closedir(dir);
Lwt.return(items);
};
let writeFile = (path, contents) => {
let%lwt x = Lwt_unix.openfile(path, [Unix.O_RDWR, Unix.O_CREAT], 777);
let%lwt _ =

4
library/Http.re

@ -14,7 +14,7 @@ let rec getBody = listOfStrings => { @@ -14,7 +14,7 @@ let rec getBody = listOfStrings => {
};
};
let rec getStatus = string => {
let getStatus = string => {
List.nth(String.split_on_char(' ', string), 1);
};
@ -27,7 +27,7 @@ let verifyStatus = response => { @@ -27,7 +27,7 @@ let verifyStatus = response => {
| 200 => Lwt.return(response)
| x when x / 100 == 4 => Lwt.fail(Not_found(response))
| x when x / 100 == 5 => Lwt.fail(Internal_server_error(response))
| x => Lwt.fail(Unknown_status_code(response))
| _ => Lwt.fail(Unknown_status_code(response))
};
};

6
library/Opt.re

@ -28,6 +28,12 @@ let toResult = (error, opt) => @@ -28,6 +28,12 @@ let toResult = (error, opt) =>
| Some(x) => Ok(x)
};
let toLwt = (error, opt) =>
switch (opt) {
| Some(x) => Lwt.return(x)
| None => Lwt.fail(error)
};
let some = x => Some(x);
let (or) = (opt, b) => fold(() => b, x => x, opt);

6
library/System.re

@ -8,6 +8,12 @@ let unix_exec = @@ -8,6 +8,12 @@ let unix_exec =
let mkdirp = destination =>
unix_exec("mkdir", ~stderr=`Dev_null, ~args=[|"-p", destination|]);
module Shell = {
type t =
| Bash
| Fish;
};
module NodeArch = {
type t =
| X32

148
library/Versions.re

@ -1,15 +1,74 @@ @@ -1,15 +1,74 @@
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 = Fs.readdir(Directories.aliases);
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,
@ -30,12 +89,13 @@ module Remote = { @@ -30,12 +89,13 @@ module Remote = {
};
let getInstalledVersionSet = () =>
Fs.readdir(Directories.nodeVersions)
|> Result.fold(_ => [||], x => x)
|> Array.fold_left(
(acc, curr) => VersionSet.add(curr, acc),
VersionSet.empty,
);
Lwt.(
catch(() => Fs.readdir(Directories.nodeVersions), _ => return([]))
>|= List.fold_left(
(acc, curr) => VersionSet.add(curr, acc),
VersionSet.empty,
)
);
let getRelativeLinksFromHTML = html =>
Soup.parse(html)
@ -105,31 +165,38 @@ let getCurrentVersion = () => @@ -105,31 +165,38 @@ let getCurrentVersion = () =>
switch (Fs.realpath(Directories.currentVersion)) {
| installationPath =>
let fullPath = Filename.dirname(installationPath);
Some(Local.{fullPath, name: Core.Filename.basename(fullPath)});
Some(
Local.{fullPath, name: Core.Filename.basename(fullPath), aliases: []},
);
| 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),
}
),
);
Lwt.(
{
let%lwt versions =
Fs.readdir(Directories.nodeVersions) >|= 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 =
Http.makeRequest("https://nodejs.org/dist/") |> Lwt.map(Http.body);
let versions = bodyString |> Remote.getRelativeLinksFromHTML;
let installedVersions = Remote.getInstalledVersionSet();
let%lwt installedVersions = Remote.getInstalledVersionSet();
versions
|> Core.List.filter(~f=x =>
@ -147,14 +214,35 @@ let getRemoteVersions = () => { @@ -147,14 +214,35 @@ let getRemoteVersions = () => {
|> 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 => {
getInstalledVersions()
|> Result.fold(
_ => Lwt.return(),
xs =>
Array.exists(x => Local.(x.name == versionName), xs)
|> (
x => x ? Lwt.fail(Already_installed(versionName)) : Lwt.return()
),
);
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();
};
};

59
package.json

@ -13,20 +13,50 @@ @@ -13,20 +13,50 @@
},
"buildDirs": {
"test": {
"require": ["fnm.lib", "rely.lib"],
"require": [
"fnm.lib",
"rely.lib"
],
"main": "TestFnm",
"name": "TestFnm.exe",
"ocamloptFlags": ["-linkall", "-g"]
"ocamloptFlags": [
"-linkall",
"-g"
]
},
"library": {
"preprocess": ["pps", "lwt_ppx", "ppx_let"],
"require": ["str", "core", "lwt", "lwt.unix", "lambdasoup", "semver"],
"preprocess": [
"pps",
"lwt_ppx",
"ppx_let"
],
"require": [
"str",
"core",
"lwt",
"lwt.unix",
"lambdasoup",
"semver"
],
"name": "fnm.lib",
"namespace": "Fnm"
},
"executable": {
"preprocess": ["pps", "lwt_ppx", "ppx_let"],
"require": ["core", "cmdliner", "lwt", "lwt.unix", "lambdasoup", "console.lib", "pastel.lib", "fnm.lib"],
"preprocess": [
"pps",
"lwt_ppx",
"ppx_let"
],
"require": [
"core",
"cmdliner",
"lwt",
"lwt.unix",
"lambdasoup",
"console.lib",
"pastel.lib",
"fnm.lib"
],
"main": "FnmApp",
"name": "fnm.exe"
}
@ -35,6 +65,7 @@ @@ -35,6 +65,7 @@
"pesy": "bash -c 'env PESY_MODE=update pesy'",
"update-fnm-package": "node ./.ci/prepare-fnm-package.js",
"verify-fnm-package": "node ./.ci/prepare-fnm-package.js --fail-on-difference",
"bootstrap": ".ci/bootstrap",
"test": "esy x TestFnm.exe",
"fmt": "bash -c 'refmt --in-place {library,executable,test}/*.re'"
},
@ -59,6 +90,20 @@ @@ -59,6 +90,20 @@
"devDependencies": {
"@opam/merlin": "*",
"prettier": "*",
"jest-diff": "24.0.0"
"jest-diff": "24.0.0",
"lint-staged": "*"
},
"lint-staged": {
"*.re": [
"esy refmt --in-place",
"git add"
],
"*.{js,md,json}": [
"esy prettier --write",
"git add"
],
"package.json": [
"esy verify-fnm-package"
]
}
}

1
test/__snapshots__/Smoke_test.4d362c3c.0.snapshot

@ -1,3 +1,4 @@ @@ -1,3 +1,4 @@
Smoke test › env
export PATH=<sfwRoot>/current/bin:$PATH
export FNM_MULTISHELL_PATH=<sfwRoot>/current

Loading…
Cancel
Save