Browse Source

Infer shells automatically, and `use` versions based on the current working 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
remotes/origin/add-simple-redirecting-site
Gal Schlezinger 6 years ago committed by GitHub
parent
commit
dc9c9ea761
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 12
      .ci/install.sh
  2. 9
      README.md
  3. 72
      executable/Env.re
  4. 21
      executable/FnmApp.re
  5. 2
      feature_tests/fish/run.fish
  6. 1
      feature_tests/use_on_cd/app/.nvmrc
  7. 82
      feature_tests/use_on_cd/run.sh
  8. 33
      library/System.re

12
.ci/install.sh

@ -97,12 +97,12 @@ setup_shell() { @@ -97,12 +97,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
elif [ "$CURRENT_SHELL" == "fish" ]; then
CONF_FILE=$HOME/.config/fish/config.fish
@ -110,12 +110,12 @@ setup_shell() { @@ -110,12 +110,12 @@ setup_shell() {
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
@ -127,12 +127,12 @@ setup_shell() { @@ -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."

9
README.md

@ -51,13 +51,13 @@ curl https://raw.githubusercontent.com/Schniz/fnm/master/.ci/install.sh | bash - @@ -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
@ -80,13 +80,14 @@ Lists the installed Node versions. @@ -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

72
executable/Env.re

@ -1,10 +1,9 @@ @@ -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 =
@ -25,8 +24,66 @@ let rec makeTemporarySymlink = () => { @@ -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();
@ -35,7 +92,8 @@ let run = (~shell, ~multishell, ~nodeDistMirror, ~fnmDir) => { @@ -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;
@ -46,7 +104,7 @@ let run = (~shell, ~multishell, ~nodeDistMirror, ~fnmDir) => { @@ -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;
@ -59,5 +117,9 @@ let run = (~shell, ~multishell, ~nodeDistMirror, ~fnmDir) => { @@ -59,5 +117,9 @@ let run = (~shell, ~multishell, ~nodeDistMirror, ~fnmDir) => {
|> Console.log;
};
if (useOnCd) {
printUseOnCd(~shell) |> Console.log;
};
Lwt.return();
};

21
executable/FnmApp.re

@ -6,13 +6,15 @@ module Commands = { @@ -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,
),
);
};
@ -149,6 +151,14 @@ let env = { @@ -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.(
@ -172,6 +182,11 @@ let env = { @@ -172,6 +182,11 @@ 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)
@ -179,6 +194,8 @@ let env = { @@ -179,6 +194,8 @@ let env = {
$ isMultishell
$ nodeDistMirror
$ fnmDir
$ shell
$ useOnCd
),
Term.info("env", ~version, ~doc, ~exits=Term.default_exits, ~man, ~sdocs),
);

2
feature_tests/fish/run.fish

@ -1,6 +1,6 @@ @@ -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

1
feature_tests/use_on_cd/app/.nvmrc

@ -0,0 +1 @@ @@ -0,0 +1 @@
8.11.3

82
feature_tests/use_on_cd/run.sh

@ -0,0 +1,82 @@ @@ -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
library/System.re

@ -11,7 +11,40 @@ let mkdirp = destination => @@ -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 = {

Loading…
Cancel
Save