Skip to content

Commit

Permalink
Infer shells automatically, and use versions based on the current w…
Browse files Browse the repository at this point in the history
…orking directory (optional) (#68)

* `use` on every `cd`, infer shell type

* Add the ability to `use` on every pwd change (bash, zsh)
* infers shell type automatically. Fixes #20
  • Loading branch information
Schniz authored Mar 4, 2019
1 parent 5f8f19a commit dc9c9ea
Show file tree
Hide file tree
Showing 8 changed files with 214 additions and 18 deletions.
12 changes: 6 additions & 6 deletions .ci/install.sh
Original file line number Diff line number Diff line change
Expand Up @@ -97,25 +97,25 @@ setup_shell() {
echo ""
echo ' # fnm'
echo ' export PATH=$HOME/.fnm:$PATH'
echo ' eval `fnm env --multi`'
echo ' eval "`fnm env --multi`"'

echo '' >> $CONF_FILE
echo '# fnm' >> $CONF_FILE
echo 'export PATH=$HOME/.fnm:$PATH' >> $CONF_FILE
echo 'eval `fnm env --multi`' >> $CONF_FILE
echo 'eval "`fnm env --multi`"' >> $CONF_FILE

elif [ "$CURRENT_SHELL" == "fish" ]; then
CONF_FILE=$HOME/.config/fish/config.fish
echo "Installing for Fish. Appending the following to $CONF_FILE:"
echo ""
echo ' # fnm'
echo ' set PATH $HOME/.fnm $PATH'
echo ' eval (fnm env --multi --fish)'
echo ' fnm env --multi | source'

echo '' >> $CONF_FILE
echo '# fnm' >> $CONF_FILE
echo 'set PATH $HOME/.fnm $PATH' >> $CONF_FILE
echo 'eval (fnm env --multi --fish)' >> $CONF_FILE
echo 'fnm env --multi | source"' >> $CONF_FILE

elif [ "$CURRENT_SHELL" == "bash" ]; then
if [ "$OS" == "Darwin" ]; then
Expand All @@ -127,12 +127,12 @@ setup_shell() {
echo ""
echo ' # fnm'
echo ' export PATH=$HOME/.fnm:$PATH'
echo ' eval `fnm env --multi`'
echo ' eval "`fnm env --multi`"'

echo '' >> $CONF_FILE
echo '# fnm' >> $CONF_FILE
echo 'export PATH=$HOME/.fnm:$PATH' >> $CONF_FILE
echo 'eval `fnm env --multi`' >> $CONF_FILE
echo 'eval "`fnm env --multi`"' >> $CONF_FILE

else
echo "Could not infer shell type. Please set up manually."
Expand Down
9 changes: 5 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,13 +51,13 @@ curl https://raw.githubusercontent.com/Schniz/fnm/master/.ci/install.sh | bash -
- Add the following line to your `.bashrc`/`.zshrc` file:

```bash
eval `fnm env --multi`
eval "`fnm env --multi`"
```

If you are using [fish shell](https://fishshell.com/), add this line to your `config.fish` file:

```fish
eval (fnm env --multi --fish)
fnm env --multi | source
```

## Usage
Expand All @@ -80,13 +80,14 @@ Lists the installed Node versions.

Lists the Node versions available to download remotely.

### `fnm env [--multi] [--fish] [--node-dist-mirror=URI] [--base-dir=DIR]`
### `fnm env [--multi] [--shell=fish|bash|zsh] [--node-dist-mirror=URI] [--use-on-cd] [--base-dir=DIR]`

Prints the required shell commands in order to configure your shell, Bash compliant by default.

- Providing `--multi` will output the multishell support, allowing a different current Node version per shell
- Providing `--fish` will output the Fish-compliant version.
- Providing `--shell=fish` will output the Fish-compliant version. Omitting it and `fnm` will try to infer the current shell based on the process tree
- Providing `--node-dist-mirror="https://npm.taobao.org/dist"` will use the Chinese mirror of Node.js
- Providing `--use-on-cd` will also output a script that will automatically change the node version if a `.nvmrc`/`.node-version` file is found
- Providing `--base-dir="/tmp/fnm"` will install and use versions in `/tmp/fnm` directory

## Future Plans
Expand Down
72 changes: 67 additions & 5 deletions executable/Env.re
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
open Fnm;

let symlinkExists = path => {
let symlinkExists = path =>
try%lwt (Lwt_unix.lstat(path) |> Lwt.map(_ => true)) {
| _ => Lwt.return(false)
};
};

let rec makeTemporarySymlink = () => {
let suggestedName =
Expand All @@ -25,8 +24,66 @@ let rec makeTemporarySymlink = () => {
};
};

let run = (~shell, ~multishell, ~nodeDistMirror, ~fnmDir) => {
let rec printUseOnCd = (~shell) =>
switch (shell) {
| System.Shell.Bash => {|
__fnmcd () {
cd $@
if [[ -f .node-version && .node-version ]]; then
echo "fnm: Found .node-version"
fnm use
elif [[ -f .nvmrc && .nvmrc ]]; then
echo "fnm: Found .nvmrc"
fnm use
fi
}
alias cd=__fnmcd
|}
| Fish => {|
function _fnm_autoload_hook --on-variable PWD --description 'Change Node version on directory change'
status --is-command-substitution; and return
if test -f .node-version
echo "fnm: Found .node-version"
fnm use
else if test -f .nvmrc
echo "fnm: Found .nvmrc"
fnm use
end
end
|}
| Zsh => {|
autoload -U add-zsh-hook
_fnm_autoload_hook () {
if [[ -f .node-version && -r .node-version ]]; then
echo "fnm: Found .node-version"
fnm use
elsif
elif [[ -f .nvmrc && -r .nvmrc ]]; then
echo "fnm: Found .nvmrc"
fnm use
fi
}
add-zsh-hook chpwd _fnm_autoload_hook \
&& _fnm_autoload_hook
|}
};

let run = (~forceShell, ~multishell, ~nodeDistMirror, ~fnmDir, ~useOnCd) => {
open Lwt;
open System.Shell;

let%lwt shell =
switch (forceShell) {
| None =>
switch%lwt (System.Shell.infer()) {
| None => Lwt.return(Bash)
| Some(shell) => Lwt.return(shell)
}
| Some(shell) => Lwt.return(shell)
};

Random.self_init();

Expand All @@ -35,7 +92,8 @@ let run = (~shell, ~multishell, ~nodeDistMirror, ~fnmDir) => {
? makeTemporarySymlink() : Lwt.return(Directories.globalCurrentVersion);

switch (shell) {
| System.Shell.Bash =>
| Bash
| Zsh =>
Printf.sprintf("export PATH=%s/bin:$PATH", path) |> Console.log;
Printf.sprintf("export %s=%s", Config.FNM_MULTISHELL_PATH.name, path)
|> Console.log;
Expand All @@ -46,7 +104,7 @@ let run = (~shell, ~multishell, ~nodeDistMirror, ~fnmDir) => {
nodeDistMirror,
)
|> Console.log;
| System.Shell.Fish =>
| Fish =>
Printf.sprintf("set PATH %s/bin $PATH;", path) |> Console.log;
Printf.sprintf("set %s %s;", Config.FNM_MULTISHELL_PATH.name, path)
|> Console.log;
Expand All @@ -59,5 +117,9 @@ let run = (~shell, ~multishell, ~nodeDistMirror, ~fnmDir) => {
|> Console.log;
};

if (useOnCd) {
printUseOnCd(~shell) |> Console.log;
};

Lwt.return();
};
21 changes: 19 additions & 2 deletions executable/FnmApp.re
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,15 @@ module Commands = {
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, isMultishell, nodeDistMirror, fnmDir) =>
let env =
(isFishShell, isMultishell, nodeDistMirror, fnmDir, shell, useOnCd) =>
Lwt_main.run(
Env.run(
~shell=Fnm.System.Shell.(isFishShell ? Fish : Bash),
~forceShell=Fnm.System.Shell.(isFishShell ? Some(Fish) : shell),
~multishell=isMultishell,
~nodeDistMirror,
~fnmDir,
~useOnCd,
),
);
};
Expand Down Expand Up @@ -149,6 +151,14 @@ let env = {
Arg.(value & flag & info(["fish"], ~doc));
};

let shell = {
open Fnm.System.Shell;
let doc = "Specifies a specific shell type. If omitted, it will be inferred based on the process tree. $(docv)";
let shellChoices =
Arg.enum([("fish", Fish), ("bash", Bash), ("zsh", Zsh)]);
Arg.(value & opt(some(shellChoices), None) & info(["shell"], ~doc));
};

let nodeDistMirror = {
let doc = "https://nodejs.org/dist mirror";
Arg.(
Expand All @@ -172,13 +182,20 @@ let env = {
Arg.(value & flag & info(["multi"], ~doc));
};

let useOnCd = {
let doc = "Hook into the shell `cd` and automatically use the specified version for the project";
Arg.(value & flag & info(["use-on-cd"], ~doc));
};

(
Term.(
const(Commands.env)
$ isFishShell
$ isMultishell
$ nodeDistMirror
$ fnmDir
$ shell
$ useOnCd
),
Term.info("env", ~version, ~doc, ~exits=Term.default_exits, ~man, ~sdocs),
);
Expand Down
2 changes: 1 addition & 1 deletion feature_tests/fish/run.fish
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
#!/usr/bin/env fish

eval (fnm env --fish)
fnm env --fish | source

fnm install v8.11.3
fnm use v8.11.3
Expand Down
1 change: 1 addition & 0 deletions feature_tests/use_on_cd/app/.nvmrc
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
8.11.3
82 changes: 82 additions & 0 deletions feature_tests/use_on_cd/run.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
#!/bin/bash

set -e

DIRECTORY=`dirname $0`

eval "`fnm env --multi`"
fnm install 6.11.3
fnm install 8.11.3
fnm use 6.11.3

if hash zsh 2>/dev/null; then
echo ' > Running test on Zsh'

zsh -c '
set -e
eval "`fnm env --multi --use-on-cd`"
fnm use 6.11.3
NODE_VERSION=$(node -v)
if [ "$NODE_VERSION" != "v6.11.3" ]; then
echo "Failed: Node version ($NODE_VERSION) is not v6.11.3"
exit 1
fi
cd app
NODE_VERSION=$(node -v)
if [ "$NODE_VERSION" != "v8.11.3" ]; then
echo "Failed: Node version ($NODE_VERSION) is not v8.11.3"
exit 1
fi
'
else
echo "Skipping zsh test: \`zsh\` is not installed"
fi

if hash fish 2>/dev/null; then
echo ' > Running test on Fish'

fish -c '
fnm env --multi --use-on-cd | source
fnm use 6.11.3
set NODE_VERSION (node -v)
if test "$NODE_VERSION" != "v6.11.3"
echo "Failed: Node version ($NODE_VERSION) is not v6.11.3"
exit 1
end
cd app
set NODE_VERSION (node -v)
if test "$NODE_VERSION" != "v8.11.3"
echo "Failed: Node version ($NODE_VERSION) is not v8.11.3"
exit 1
end
'
else
echo "Skipping fish test: \`zsh\` is not installed"
fi

echo " > Running test on Bash..."
bash -c '
shopt -s expand_aliases
eval "`fnm env --multi --use-on-cd`"
fnm use 6.11.3
NODE_VERSION=$(node -v)
if [ "$NODE_VERSION" != "v6.11.3" ]; then
echo "Failed: Node version ($NODE_VERSION) is not v6.11.3"
exit 1
fi
cd app
NODE_VERSION=$(node -v)
if [ "$NODE_VERSION" != "v8.11.3" ]; then
echo "Failed: Node version ($NODE_VERSION) is not v8.11.3"
exit 1
fi
'
33 changes: 33 additions & 0 deletions library/System.re
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,40 @@ let mkdirp = destination =>
module Shell = {
type t =
| Bash
| Zsh
| Fish;

let infer = () => {
let processInfo = pid => {
switch%lwt (unix_exec("ps", ~args=[|"-o", "ppid,comm", pid|])) {
| [] => Lwt.return_none
| [_headers, line, ..._otherLines] =>
let psResult = String.split_on_char(' ', line |> String.trim);
let parentPid = List.nth(psResult, 0);
let executable = List.nth(psResult, 1) |> Filename.basename;
Lwt.return_some((parentPid, executable));
| [_, ...xs] => Lwt.return_none
};
};

let rec getShell = (~level=0, pid) => {
switch%lwt (processInfo(pid)) {
| Some((_, "sh"))
| Some((_, "-sh"))
| Some((_, "-bash"))
| Some((_, "bash")) => Lwt.return_some(Bash)
| Some((_, "-zsh"))
| Some((_, "zsh")) => Lwt.return_some(Zsh)
| Some((_, "fish"))
| Some((_, "-fish")) => Lwt.return_some(Fish)
| Some((ppid, _)) when level < 10 => getShell(~level=level + 1, ppid)
| Some(_)
| None => Lwt.return_none
};
};

getShell(Unix.getpid() |> string_of_int);
};
};

module NodeArch = {
Expand Down

0 comments on commit dc9c9ea

Please sign in to comment.