diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 1dfc391..b73bc7e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -3,18 +3,24 @@ name: CI on: push jobs: - build: + test: runs-on: ubuntu-latest + steps: - uses: actions/checkout@v2 - - run: | - sudo add-apt-repository -y ppa:fish-shell/nightly-master + - name: Install Fish + run: | + sudo apt-add-repository -yn ppa:fish-shell/release-3 sudo apt-get update - sudo apt-get -y install fish - - run: | - curl git.io/fisher --create-dirs -sLo ~/.config/fish/functions/fisher.fish - fish -c "fisher add . && nvm use lts && node -v" - - + sudo apt-get install -y fish + - name: Install Fisher + run: curl -sL https://git.io/fisher | source && fisher install jorgebucaran/fisher + shell: fish {0} + - name: Install NVM + run: | + fisher install $GITHUB_WORKSPACE + nvm install 6 + test (nvm current) = v6.17.1 + shell: fish {0} diff --git a/README.md b/README.md index fc64a1a..4c01fac 100644 --- a/README.md +++ b/README.md @@ -1,125 +1,100 @@ # nvm.fish -> 100% pure-fish Node.js version manager. +> Node version management lovingly made for [Fish](https://fishshell.com). -- `.nvmrc` support. -- Seamless shell integration. Tab-completions? You got them. -- No dependencies, no subshells, no configuration setup, no fluff—it's so easy it hurts. -- Because nvm.fish runs natively by fish, it's insanely fast ([see this discussion](https://github.com/jorgebucaran/fish-nvm/issues/82)). +Not [_that_](https://github.com/nvm-sh/nvm) POSIX-compatible script. Designed for [Fish](https://fishshell.com), this tool helps you manage multiple active versions of Node on a single local environment. Quickly install and switch between runtimes without cluttering your home directory or breaking system-wide scripts. -![](https://gistcdn.githack.com/jorgebucaran/00f6d3f301483a01a00e836eb17a2b3e/raw/26625256b5e5ccb632f990727db70055ae24e584/nvm.fish.svg) +- No dependencies, no setup, no clutter—it just works. +- 100% Fish—quick & easy to contribute to or change. +- Tab-completable seamless shell integration. +- `.node-version` and `.nvmrc` support. ✅ +- [XDG Base Directory](https://specifications.freedesktop.org/basedir-spec/basedir-spec-latest.html) compliant. ## Installation -Install with [Fisher](https://github.com/jorgebucaran/fisher) (recommended): +Install with [Fisher](https://github.com/jorgebucaran/fisher): ```console fisher install jorgebucaran/nvm.fish ``` -
-Not using a package manager? - -### +## Quickstart -Copy [`conf.d/nvm.fish`](conf.d/nvm.fish), [`functions/nvm.fish`](functions/nvm.fish), and [`completions/nvm.fish`](completions/nvm.fish) to your fish configuration directory preserving the directory structure. +Install the latest Node release and start using it. -```fish -set -q XDG_CONFIG_HOME; or set XDG_CONFIG_HOME ~/.config - -for i in conf.d functions completions - curl https://git.io/$i.nvm.fish --create-dirs -sLo $XDG_CONFIG_HOME/fish/$i/nvm.fish -end +```console +nvm install latest ``` -To uninstall nvm, just run: +Install the latest [LTS](https://github.com/nodejs/Release) (long-term support) Node release. +```console +nvm install lts ``` -rm -f $XDG_CONFIG_HOME/fish/{conf.d,functions,completions}/nvm.fish && emit nvm_uninstall -``` - -
- -## Quickstart -Download and switch to the latest Node.js release. +Install an older LTS release by codename. ```console -nvm use latest +nvm install carbon ``` -> **Note:** This downloads the latest Node.js release tarball from the [official mirror](https://nodejs.org/dist), extracts it to [\$XDG_CONFIG_HOME](https://specifications.freedesktop.org/basedir-spec/basedir-spec-latest.html#variables)/nvm and modifies your `$PATH` so it can be used immediately. Learn more about the Node.js release schedule [here](https://github.com/nodejs/Release). +> Installs `8.16.2`, the latest release of the Carbon LTS line. -Download and switch to the latest LTS (long-term support) Node.js release. +Or install a specific version of Node. ```console -nvm use lts +nvm install v12.9.1 ``` -You can create a `.nvmrc` file in the root of your project (or any parent directory) and run `nvm` to use the version in it. `nvm` will try to find the nearest `.nvmrc` file, traversing the directory tree from the current working directory upwards. +> Supports full or partial version numbers, starting with an optional "v". -```console -node -v > .nvmrc -nvm +The `nvm install` command activates the specified Node version only in the current environment. If you want to set the default version for new shells use: + +```fish +set --universal nvm_default_version v12.9.1 ``` -Run `nvm` in any subdirectory of a directory with an `.nvmrc` file to switch to the version from that file. Similarly, running `nvm use ` updates that `.nvmrc` file with the specified version. +Activate a version you've already installed. ```console -├── README.md -├── dist -├── node_modules -├── package.json -└── src - └── index.js +nvm use lts ``` +List which versions you have installed (includes any previously installed system Node if there is one). + ```console -echo lts >.nvmrc -cd src -nvm -node -v -v10.15.1 +$ nvm list + system + v8.17.0 lts/carbon + v12.9.1 + ▶ v14.15.1 lts/fermium + v15.3.0 latest ``` -### Listing versions - -List all the supported Node.js versions you can download and switch to. +Or list all the Node versions available to install. ```console -nvm ls +nvm list-remote ``` +Want to remove a Node version? You can do that too. + ```console -... -10.14.2 (lts/dubnium) -10.15.0 (lts/dubnium) - 11.0.0 - 11.1.0 - 11.2.0 - 11.3.0 - 11.4.0 - 11.5.0 - 11.6.0 - 11.7.0 (latest/current) +nvm remove v12.9.1 ``` -You can use a regular expression to narrow down the output. +## `.nvmrc` -```console -nvm ls '^8.[4-6]' -``` +An `.nvmrc` file makes it easy to peg a specific version of Node for different projects. Just create an `.nvmrc` (or `.node-version`) file containing a version number or alias, e.g., `latest`, `lts`, `carbon`, in the root of your project. ```console -8.4.0 (lts/carbon) -8.5.0 (lts/carbon) -8.6.0 (lts/carbon) +node -v >.nvmrc ``` -To customize the download mirror, e.g., if you are behind a firewall, you can set `$nvm_mirror`: +Then run `nvm install` to install or `nvm use` to activate that version. Works from anywhere inside your project by traversing the directory hierarchy until an `.nvmrc` is found! ```console -set -g nvm_mirror http://npm.taobao.org/mirrors/node +nvm install ``` ## License diff --git a/completions/nvm.fish b/completions/nvm.fish index 505ead2..0a77849 100644 --- a/completions/nvm.fish +++ b/completions/nvm.fish @@ -1,6 +1,19 @@ -complete -xc nvm -n __fish_use_subcommand -a ls -d "List available versions matching " -complete -xc nvm -n __fish_use_subcommand -a use -d "Download and modify PATH so it's available" -complete -xc nvm -n __fish_use_subcommand -a --help -d "Show usage help" -complete -xc nvm -n __fish_use_subcommand -a --version -d "Show the current version of nvm" +complete -c nvm --exclusive --long version -d "Print version" +complete -c nvm --exclusive --long help -d "Print this help message" -nvm complete +complete -c nvm --exclusive --condition "__fish_use_subcommand" -a install -d "Download and activate a given version (use nearest .nvmrc file if none is given)" +complete -c nvm --exclusive --condition "__fish_use_subcommand" -a use -d "Activate a version in the current shell" +complete -c nvm --exclusive --condition "__fish_use_subcommand" -a list -d "List installed versions" +complete -c nvm --exclusive --condition "__fish_use_subcommand" -a list-remote -d "List versions available to install (matching optional regex)" +complete -c nvm --exclusive --condition "__fish_use_subcommand" -a current -d "Print currently-active version" +complete -c nvm --exclusive --condition "__fish_seen_subcommand_from install" -a "( + test -e $nvm_data && string split ' ' <$nvm_data/.index +)" +complete -c nvm --exclusive --condition "__fish_seen_subcommand_from use" -a "(_nvm_list | string split ' ')" +complete -c nvm --exclusive --condition "__fish_use_subcommand" -a uninstall -d "Uninstall a version" +complete -c nvm --exclusive --condition "__fish_seen_subcommand_from uninstall" -a "( + _nvm_list | string split ' ' | string replace system '' +)" +complete -c nvm --exclusive --condition "__fish_seen_subcommand_from use uninstall" -a "( + set --query nvm_default_version && echo default +)" \ No newline at end of file diff --git a/conf.d/nvm.fish b/conf.d/nvm.fish index 40827fd..9ece905 100644 --- a/conf.d/nvm.fish +++ b/conf.d/nvm.fish @@ -1,15 +1,23 @@ -function _nvm_uninstall -e nvm_uninstall - if test -s "$nvm_config/version" - read -l ver <$nvm_config/version - if set -l i (contains -i -- "$nvm_config/$ver/bin" $fish_user_paths) - set -e fish_user_paths[$i] - end - command rm -f $nvm_config/version - end - - for name in (set -n | command awk '/^nvm_/') - set -e "$name" - end - - functions -e (functions -a | command awk '/^_nvm_/') +set --query XDG_DATA_HOME \ + && set --global nvm_data $XDG_DATA_HOME/nvm \ + || set --global nvm_data ~/.local/share/nvm +set --query nvm_mirror || set --global nvm_mirror https://nodejs.org/dist + +if set --query nvm_default_version && ! set --query nvm_current_version + nvm use $nvm_default_version >/dev/null +end + +function _nvm_install -e nvm_install + test ! -d $nvm_data && command mkdir -p $nvm_data + echo "Downloading the Node distribution index for the first time..." 2>/dev/null + _nvm_index_update $nvm_mirror/index.tab $nvm_data/.index end + +function _nvm_uninstall -e nvm_uninstall + command rm -rf $nvm_data + set --query nvm_current_version && _nvm_version_deactivate $nvm_current_version + + set --names | string replace --filter --regex "^nvm_" -- "set --erase nvm_" | source + functions --erase (functions --all | string match --entire --regex "^_nvm_") + complete --erase --command nvm +end \ No newline at end of file diff --git a/functions/_nvm_index_update.fish b/functions/_nvm_index_update.fish new file mode 100644 index 0000000..e13d87d --- /dev/null +++ b/functions/_nvm_index_update.fish @@ -0,0 +1,12 @@ +function _nvm_index_update -a mirror index + command curl --location --silent $mirror | command awk -v OFS=\t ' + /v0.9.12/ { exit } # Unsupported + NR > 1 { + print $1 (NR == 2 ? " latest" : $10 != "-" ? " lts/" tolower($10) : "") + } + ' > $index.temp && command mv $index.temp $index && return + + command rm -f $index.temp + echo "nvm: Invalid index or unavailable host: \"$mirror\"" >&2 + return 1 +end \ No newline at end of file diff --git a/functions/_nvm_list.fish b/functions/_nvm_list.fish new file mode 100644 index 0000000..8a3f3e0 --- /dev/null +++ b/functions/_nvm_list.fish @@ -0,0 +1,11 @@ +function _nvm_list + set --local versions $nvm_data/* + set --query versions[1] \ + && string match --entire --regex (string match --regex "v\d.+" $versions \ + | string escape --style=regex \ + | string join "|" + ) <$nvm_data/.index + + command --all node | string match --quiet --invert --regex "^$nvm_data" \ + && echo system +end \ No newline at end of file diff --git a/functions/_nvm_version_activate.fish b/functions/_nvm_version_activate.fish new file mode 100644 index 0000000..9f274f3 --- /dev/null +++ b/functions/_nvm_version_activate.fish @@ -0,0 +1,4 @@ +function _nvm_version_activate -a v + set --global --export nvm_current_version $v + set --prepend PATH $nvm_data/$v/bin +end \ No newline at end of file diff --git a/functions/_nvm_version_deactivate.fish b/functions/_nvm_version_deactivate.fish new file mode 100644 index 0000000..8310882 --- /dev/null +++ b/functions/_nvm_version_deactivate.fish @@ -0,0 +1,5 @@ +function _nvm_version_deactivate -a v + test "$nvm_current_version" = "$v" && set --erase nvm_current_version + set --local index (contains --index -- $nvm_data/$v/bin $PATH) \ + && set --erase PATH[$index] +end \ No newline at end of file diff --git a/functions/nvm.fish b/functions/nvm.fish index 7328e01..796218b 100644 --- a/functions/nvm.fish +++ b/functions/nvm.fish @@ -1,232 +1,188 @@ -set -g nvm_version 1.1.0 - -function nvm -a cmd -d "Node.js version manager" - set -q XDG_CONFIG_HOME; or set XDG_CONFIG_HOME ~/.config - set -g nvm_config $XDG_CONFIG_HOME/nvm - set -g nvm_file .nvmrc - set -q nvm_mirror; or set -g nvm_mirror "https://nodejs.org/dist" - - if test ! -d $nvm_config - command mkdir -p $nvm_config +function nvm -a cmd v -d "Node version manager" + if test -z "$v" && contains -- "$cmd" install use + for file in .nvmrc .node-version + set file (_nvm_find_up $PWD $file) && read v <$file && break + end + if test -z "$v" + echo "nvm: Invalid version or missing \".nvmrc\" file" >&2 + return 1 + end end switch "$cmd" - case ls list - set -e argv[1] - _nvm_ls $argv - case use - set -e argv[1] - _nvm_use $argv - case "" - if isatty - if set -l root (_nvm_find_up (pwd) $nvm_file) - read cmd <$root/$nvm_file - end - else - read cmd - end - if not set -q cmd[1] - echo "nvm: version or .nvmrc file missing" >&2 - _nvm_help >&2 + case -v --version + echo "nvm, version 2.0.0" + case "" -h --help + echo "usage: nvm install Download and activate a given version" + echo " nvm install Install version from nearest .nvmrc file" + echo " nvm use Activate a version in the current shell" + echo " nvm use Activate version from nearest .nvmrc file" + echo " nvm list List installed versions" + echo " nvm list-remote List versions available to install" + echo " nvm list-remote List versions matching a given regular expression" + echo " nvm current Print currently-active version" + echo " nvm uninstall Uninstall a version" + echo "options:" + echo " -v or --version Print version" + echo " -h or --help Print this help message" + echo "variables:" + echo " nvm_mirror Set mirror for Node binaries" + echo " nvm_default_version Set the default version for new shells" + case install + _nvm_index_update $nvm_mirror/index.tab $nvm_data/.index || return + + string match --entire --regex (_nvm_version_match $v) <$nvm_data/.index | read v alias + + if ! set --query v[1] + echo "nvm: Invalid version number or alias: \"$argv[2..-1]\"" >&2 return 1 end - _nvm_use $cmd - case {,-}-v{ersion,} - echo "nvm version $nvm_version" - case {,-}-h{elp,} - _nvm_help - case complete - _nvm_complete "$nvm_config/index" - case \* - echo "nvm: unknown flag or command \"$cmd\"" >&2 - _nvm_help >&2 - return 1 - end -end + if test ! -e $nvm_data/$v + set --local arch (uname -m) + set --local os (string lower (uname -s)) + + switch $os + case linux + switch $arch + case x86_64 + set arch x64 + case armv6 armv6l + set arch armv6l + case armv7 armv7l + set arch armv7l + case armv8 armv8l aarch64 + set arch arm64 + case \* + echo "nvm: Unsupported hardware architecture: \"$arch\"" >&2 + return 1 + end + case darwin + set arch x64 + case \* + echo "nvm: Unsupported operating system: \"$os\"" >&2 + return 1 + end -function _nvm_help - echo "usage: nvm --help Show this help" - echo " nvm --version Show the current version of nvm" - echo " nvm ls [] List available versions matching " - echo " nvm use Download and modify PATH to use it" - echo " nvm Use version in .nvmrc (or stdin if not a tty)" - echo "examples:" - echo " nvm use 14" - echo " nvm use lts" - echo " nvm use latest" - echo " nvm use dubnium" - echo " nvm ls '^1|9\$'" - echo " nvm ls 12" - echo " nvm 1 && !/^v0\.[1-9]\./ { - split($1 = substr($1, 2), v, ".") - is_latest = NR == 2 - alias = ($10 = tolower($10)) == "-" ? "" : "lts|"$10 - is_lts = alias != "" - print $1, (/^0/ ? "-" : v[1]), v[1]"."v[2], - is_latest ? is_lts ? alias"|latest" : "latest" : is_lts ? alias : "" - } - ' >$index 2>/dev/null + echo -e "Installing Node \x1b[1m$v\x1b[22m $alias" + echo -e "Fetching \x1b[4m$url\x1b[24m\x1b[7m" - if test ! -s "$index" - echo "nvm: invalid mirror index -- is \"$nvm_mirror\" a valid host?" >&2 - return 1 - end + if ! command curl --progress-bar --location $url \ + | command tar --extract --gzip --directory $nvm_data/$v 2>/dev/null + command rm -rf $nvm_data/$v + echo -e "\033[F\33[2K\x1b[0mnvm: Invalid mirror or host unavailable: \"$url\"" >&2 + return 1 + end - _nvm_complete $index - set -g nvm_index_update_interval (command date +%s) - end + echo -en "\033[F\33[2K\x1b[0m" - echo $index -end + command mv $nvm_data/$v/$dir/* $nvm_data/$v + command rm -rf $nvm_data/$v/$dir + end -function _nvm_ls -a query - set -l index (_nvm_get_index); or return - test -s "$nvm_config/version"; and read -l current <"$nvm_config/version" - command awk -v current="$current" ' - $1 ~ /'"$query"'/ { - gsub(/\|/, "/", $4) - out[n++] = $1 - out[n++] = $4 ($1 == current ? ($4 ? "/" : "") "current" : "") - pad = pad < length($1) ? length($1) : pad - } - END { - for (i = n - 1; i > 0; i -= 2) { - printf("%"pad"s %s\n", out[i - 1] , out[i] ? "("out[i]")": "") - } - } - ' <$index 2>/dev/null -end + if test $v != "$nvm_current_version" + set --query nvm_current_version && _nvm_version_deactivate $nvm_current_version + _nvm_version_activate $v + end -function _nvm_resolve_version - set -l index (_nvm_get_index); or return - set -l ver (command awk -v ver="$argv[1]" ' - BEGIN { - if (match(ver, /v[0-9]/)) gsub(/^[ \t]*v|[ \t]*$/, "", ver) - if ((n = split(tolower(ver), a, "/")) > 3) exit - for (ver = a[1]; n > 0; n--) { - if (a[n] != "*" && a[n] != "latest" && (ver = a[n]) != "lts") - break - } - } - ver == $1"" || ver == $2"" || ver == $3"" || $4 && ver ~ $4 { - print $1 - exit - } - ' <$index 2>/dev/null) + printf "Now using Node %s (npm %s) %s\n" (_nvm_node_info) + case use + test $v = default && set v $nvm_default_version + _nvm_list | string match --entire --regex (_nvm_version_match $v) | read v __ - if not set -q ver[1] - return 1 - end + if ! set --query v[1] + echo "nvm: Node version not installed or invalid: \"$argv[2..-1]\"" >&2 + return 1 + end - echo $ver -end + if test $v != "$nvm_current_version" + set --query nvm_current_version && _nvm_version_deactivate $nvm_current_version + test $v != system && _nvm_version_activate $v + end -function _nvm_use - set -l index (_nvm_get_index); or return - set -l ver (_nvm_resolve_version $argv[1]) + printf "Now using Node %s (npm %s) %s\n" (_nvm_node_info) + case uninstall + if test -z "$v" + echo "nvm: Not enough arguments for command: \"$cmd\"" >&2 + return 1 + end - if not set -q ver[1] - echo "nvm: invalid version number or alias: \"$argv[1]\"" >&2 - return 1 - end + test $v = default && test ! -z "$nvm_default_version" && set v $nvm_default_version - if test ! -d "$nvm_config/$ver/bin" - set -l os - set -l arch - set -l name "node-v$ver" - set -l target "$nvm_config/$ver" - switch (uname -s) - case Linux - set os linux - switch (uname -m) - case x86_64 - set arch x64 - case armv6 armv6l - set arch armv6l - case armv7 armv7l - set arch armv7l - case armv8 armv8l aarch64 - set arch arm64 - case \* - set arch x86 - end - set name "$name-linux-$arch.tar.gz" - case Darwin - set os darwin - set arch x64 - case \* - echo "nvm: OS not implemented -- request it on https://git.io/fish-nvm" >&2 + _nvm_list | string match --entire --regex (_nvm_version_match $v) | read v __ + + if ! set -q v[1] + echo "nvm: Node version not installed or invalid: \"$argv[2..-1]\"" >&2 return 1 - end + end - set -l name "node-v$ver-$os-$arch" - set -l url $nvm_mirror/v$ver/$name + echo -e "Uninstalling Node $v "(command --search node | string replace ~ \~) - echo "fetching $url" >&2 - command mkdir -p $target/$name + _nvm_version_deactivate $v - if not command curl -L --fail --progress-bar $url.tar.gz | command tar -xzf- -C $target/$name - command rm -rf $target - echo "nvm: fetch error -- are you offline?" >&2 + command rm -rf $nvm_data/$v + case current + _nvm_current + case ls list + _nvm_list | _nvm_list_format (_nvm_current) $argv[2] + case lsr {ls,list}-remote + _nvm_index_update $nvm_mirror/index.tab $nvm_data/.index || return + _nvm_list | command awk ' + FNR == NR && FILENAME == "-" { + is_local[$1]++ + next + } { print $0 (is_local[$1] ? " ✓" : "") } + ' - $nvm_data/.index | _nvm_list_format (_nvm_current) $argv[2] + case \* + echo "nvm: Unknown flag or command: \"$cmd\" (see `nvm -h`)" >&2 return 1 - end - - command mv -f $target/$name/$name $nvm_config/$ver. - command rm -rf $target - command mv -f $nvm_config/$ver. $target - end - - if test -s "$nvm_config/version" - read -l last <"$nvm_config/version" - if set -l i (contains -i -- "$nvm_config/$last/bin" $fish_user_paths) - set -e fish_user_paths[$i] - end end +end - if set -l root (_nvm_find_up (pwd) $nvm_file) - read -l line <$root/$nvm_file - if test $ver != (_nvm_resolve_version $line) - echo $argv[1] >$root/$nvm_file - end +function _nvm_find_up -a path file + test -e "$path/$file" && echo $path/$file || begin + test "$path" != / || return + _nvm_find_up (command dirname $path) $file end +end - echo $ver >$nvm_config/version +function _nvm_version_match -a v + string replace --regex '^v?(\d+|\d+\.\d+)$' 'v$1.' $v \ + | string replace --filter --regex '^v?(\d+)' 'v$1' \ + | string escape --style=regex || string lower '\b'$v'(?:/\w+)?$' +end - if not contains -- "$nvm_config/$ver/bin" $fish_user_paths - set -U fish_user_paths "$nvm_config/$ver/bin" $fish_user_paths - end +function _nvm_list_format -a current filter + command awk -v current="$current" -v filter="$filter" ' + $0 ~ filter { + len = ++i + indent = (n = length($1)) > indent ? n : indent + versions[len] = $1 + aliases[len] = $2 " " $3 + } + END { + for (i = len; i > 0; i--) { + printf((current == versions[i] ? " ▶ " : " ") "%"indent"s %s\n", versions[i], aliases[i]) + } + exit (len == 0) + } + ' end -function _nvm_find_up -a path file - if test -e "$path/$file" - echo $path - else if test "$path" != / - _nvm_find_up (command dirname $path) $file - else - return 1 - end +function _nvm_current + command --search --quiet node || return + set --query nvm_current_version && echo $nvm_current_version || echo system end + +function _nvm_node_info + set --local npm_pkg_json (realpath (command --search npm)) + command node --eval " + console.log(process.version) + console.log(require('"(string replace bin/npm-cli.js package.json $npm_pkg_json)"').version)" + command --search node | string replace ~ \~ +end \ No newline at end of file