Browse Source

Merge branch 'master' into list-filter

remotes/origin/list-filter
Gal Schlezinger 1 year ago committed by GitHub
parent
commit
d587f61407
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 5
      .changeset/poor-otters-cheer.md
  2. 5
      .changeset/poor-poets-compete.md
  3. 5
      .changeset/show-download-progress.md
  4. 8
      .ci/install.sh
  5. 6
      .ci/record_screen.sh
  6. 151
      .github/workflows/rust.yml
  7. 81
      Cargo.lock
  8. 2
      Cargo.toml
  9. 29
      README.md
  10. 58
      docs/commands.md
  11. 72
      docs/configuration.md
  12. 3
      rust-toolchain.toml
  13. 7
      src/arch.rs
  14. 4
      src/archive/mod.rs
  15. 2
      src/commands/env.rs
  16. 16
      src/commands/install.rs
  17. 2
      src/commands/use.rs
  18. 4
      src/config.rs
  19. 19
      src/downloader.rs
  20. 1
      src/main.rs
  21. 165
      src/progress.rs
  22. 6
      src/shell/bash.rs
  23. 6
      src/shell/fish.rs
  24. 2
      src/shell/infer/mod.rs
  25. 6
      src/shell/powershell.rs
  26. 6
      src/shell/zsh.rs

5
.changeset/poor-otters-cheer.md

@ -0,0 +1,5 @@
---
"fnm": patch
---
support `x64-musl` arch by adding a `--arch x64-musl` to fnm env

5
.changeset/poor-poets-compete.md

@ -0,0 +1,5 @@
---
"fnm": patch
---
make nicer styling in progress bar (add newline, make it unicode)

5
.changeset/show-download-progress.md

@ -0,0 +1,5 @@
---
"fnm": minor
---
Show a progress bar when downloading and extracting node

8
.ci/install.sh

@ -5,6 +5,10 @@ set -e
RELEASE="latest" RELEASE="latest"
OS="$(uname -s)" OS="$(uname -s)"
case "${OS}" in
MINGW* | Win*) OS="Windows" ;;
esac
if [ -d "$HOME/.fnm" ]; then if [ -d "$HOME/.fnm" ]; then
INSTALL_DIR="$HOME/.fnm" INSTALL_DIR="$HOME/.fnm"
elif [ -n "$XDG_DATA_HOME" ]; then elif [ -n "$XDG_DATA_HOME" ]; then
@ -70,6 +74,9 @@ set_filename() {
elif [ "$OS" = "Darwin" ]; then elif [ "$OS" = "Darwin" ]; then
USE_HOMEBREW="true" USE_HOMEBREW="true"
echo "Downloading fnm using Homebrew..." echo "Downloading fnm using Homebrew..."
elif [ "$OS" = "Windows" ] ; then
FILENAME="fnm-windows"
echo "Downloading the latest fnm binary from GitHub..."
else else
echo "OS $OS is not supported." echo "OS $OS is not supported."
echo "If you think that's a bug - please file an issue to https://github.com/Schniz/fnm/issues" echo "If you think that's a bug - please file an issue to https://github.com/Schniz/fnm/issues"
@ -180,6 +187,7 @@ setup_shell() {
echo ' set PATH "'"$INSTALL_DIR"'" $PATH' echo ' set PATH "'"$INSTALL_DIR"'" $PATH'
echo ' fnm env | source' echo ' fnm env | source'
echo '' >>$CONF_FILE
echo '# fnm' >>$CONF_FILE echo '# fnm' >>$CONF_FILE
echo 'set PATH "'"$INSTALL_DIR"'" $PATH' >>$CONF_FILE echo 'set PATH "'"$INSTALL_DIR"'" $PATH' >>$CONF_FILE
echo 'fnm env | source' >>$CONF_FILE echo 'fnm env | source' >>$CONF_FILE

6
.ci/record_screen.sh

@ -20,7 +20,11 @@ RECORDING_PATH=$DIRECTORY/screen_recording
(rm -rf "$RECORDING_PATH" &> /dev/null || true) (rm -rf "$RECORDING_PATH" &> /dev/null || true)
asciinema rec -c "$DIRECTORY/recorded_screen_script.sh" "$RECORDING_PATH" asciinema rec \
--command "$DIRECTORY/recorded_screen_script.sh" \
--cols 70 \
--rows 17 \
"$RECORDING_PATH"
sed "s@$TEMP_DIR@~@g" "$RECORDING_PATH" | \ sed "s@$TEMP_DIR@~@g" "$RECORDING_PATH" | \
svg-term \ svg-term \
--window \ --window \

151
.github/workflows/rust.yml

@ -10,13 +10,16 @@ concurrency:
group: ci-${{ github.head_ref }} group: ci-${{ github.head_ref }}
cancel-in-progress: true cancel-in-progress: true
env:
RUST_VERSION: "1.78"
jobs: jobs:
fmt: fmt:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: hecrj/setup-rust-action@v1 - uses: hecrj/setup-rust-action@v1
with: with:
rust-version: stable rust-version: ${{env.RUST_VERSION}}
- uses: Swatinem/rust-cache@v2 - uses: Swatinem/rust-cache@v2
- uses: actions/checkout@v3 - uses: actions/checkout@v3
- name: cargo fmt - name: cargo fmt
@ -27,7 +30,7 @@ jobs:
steps: steps:
- uses: hecrj/setup-rust-action@v1 - uses: hecrj/setup-rust-action@v1
with: with:
rust-version: stable rust-version: ${{env.RUST_VERSION}}
- uses: Swatinem/rust-cache@v2 - uses: Swatinem/rust-cache@v2
- uses: actions/checkout@v3 - uses: actions/checkout@v3
- name: cargo clippy - name: cargo clippy
@ -41,7 +44,7 @@ jobs:
steps: steps:
- uses: hecrj/setup-rust-action@v1 - uses: hecrj/setup-rust-action@v1
with: with:
rust-version: stable rust-version: ${{env.RUST_VERSION}}
- uses: Swatinem/rust-cache@v2 - uses: Swatinem/rust-cache@v2
- uses: actions/checkout@v3 - uses: actions/checkout@v3
- name: Run tests - name: Run tests
@ -53,7 +56,7 @@ jobs:
steps: steps:
- uses: hecrj/setup-rust-action@v1 - uses: hecrj/setup-rust-action@v1
with: with:
rust-version: stable rust-version: ${{env.RUST_VERSION}}
- uses: Swatinem/rust-cache@v2 - uses: Swatinem/rust-cache@v2
- uses: actions/checkout@v3 - uses: actions/checkout@v3
- name: Build release binary - name: Build release binary
@ -71,7 +74,7 @@ jobs:
steps: steps:
- uses: hecrj/setup-rust-action@v1 - uses: hecrj/setup-rust-action@v1
with: with:
rust-version: stable rust-version: ${{env.RUST_VERSION}}
- uses: Swatinem/rust-cache@v2 - uses: Swatinem/rust-cache@v2
- uses: actions/checkout@v3 - uses: actions/checkout@v3
- name: Build release binary - name: Build release binary
@ -237,7 +240,7 @@ jobs:
steps: steps:
- uses: hecrj/setup-rust-action@v1 - uses: hecrj/setup-rust-action@v1
with: with:
rust-version: stable rust-version: ${{env.RUST_VERSION}}
targets: x86_64-unknown-linux-musl targets: x86_64-unknown-linux-musl
- uses: Swatinem/rust-cache@v2 - uses: Swatinem/rust-cache@v2
with: with:
@ -278,7 +281,7 @@ jobs:
uses: docker/setup-qemu-action@v2 uses: docker/setup-qemu-action@v2
- uses: hecrj/setup-rust-action@v1 - uses: hecrj/setup-rust-action@v1
with: with:
rust-version: stable rust-version: ${{env.RUST_VERSION}}
- uses: Swatinem/rust-cache@v2 - uses: Swatinem/rust-cache@v2
with: with:
key: arm-binary-${{ matrix.arch }} key: arm-binary-${{ matrix.arch }}
@ -358,70 +361,70 @@ jobs:
run: | run: |
pnpm run generate-command-docs --check --binary-path=$(which fnm) pnpm run generate-command-docs --check --binary-path=$(which fnm)
run_e2e_benchmarks: # TODO: use bnz
runs-on: ubuntu-latest # run_e2e_benchmarks:
name: bench/linux # runs-on: ubuntu-latest
needs: [build_static_linux_binary] # name: bench/linux
permissions: # needs: [build_static_linux_binary]
contents: write # permissions:
pull-requests: write # contents: write
steps: # pull-requests: write
- name: install necessary shells # steps:
run: sudo apt-get update && sudo apt-get install -y fish zsh bash hyperfine # - name: install necessary shells
- uses: actions/checkout@v3 # run: sudo apt-get update && sudo apt-get install -y fish zsh bash hyperfine
- uses: actions/download-artifact@v3 # - uses: actions/checkout@v3
with: # - uses: actions/download-artifact@v3
name: fnm-linux # with:
path: target/release # name: fnm-linux
- name: mark binary as executable # path: target/release
run: chmod +x target/release/fnm # - name: mark binary as executable
- name: install fnm as binary # run: chmod +x target/release/fnm
run: | # - name: install fnm as binary
sudo install target/release/fnm /bin # run: |
fnm --version # sudo install target/release/fnm /bin
- uses: pnpm/action-setup@v2.2.4 # fnm --version
with: # - uses: pnpm/action-setup@v2.2.4
run_install: false # with:
- uses: actions/setup-node@v3 # run_install: false
with: # - uses: actions/setup-node@v3
node-version: 18.x # with:
cache: "pnpm" # node-version: 18.x
- name: Get pnpm store directory # cache: "pnpm"
id: pnpm-cache # - name: Get pnpm store directory
run: | # id: pnpm-cache
echo "::set-output name=pnpm_cache_dir::$(pnpm store path)" # run: |
- uses: actions/cache@v3 # echo "::set-output name=pnpm_cache_dir::$(pnpm store path)"
name: Setup pnpm cache # - uses: actions/cache@v3
with: # name: Setup pnpm cache
path: ${{ steps.pnpm-cache.outputs.pnpm_cache_dir }} # with:
key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }} # path: ${{ steps.pnpm-cache.outputs.pnpm_cache_dir }}
restore-keys: | # key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
${{ runner.os }}-pnpm-store- # restore-keys: |
- run: pnpm install # ${{ runner.os }}-pnpm-store-
- name: Run benchmarks # - run: pnpm install
env: # - name: Run benchmarks
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # env:
SHOULD_STORE: ${{ toJson(!github.event.pull_request) }} # GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
id: benchmark # SHOULD_STORE: ${{ toJson(!github.event.pull_request) }}
run: | # id: benchmark
delimiter="$(openssl rand -hex 8)" # run: |
echo "markdown<<${delimiter}" >> "${GITHUB_OUTPUT}" # delimiter="$(openssl rand -hex 8)"
node benchmarks/run.mjs --store=$SHOULD_STORE >> "${GITHUB_OUTPUT}" # echo "markdown<<${delimiter}" >> "${GITHUB_OUTPUT}"
echo "${delimiter}" >> "${GITHUB_OUTPUT}" # node benchmarks/run.mjs --store=$SHOULD_STORE >> "${GITHUB_OUTPUT}"
# echo "${delimiter}" >> "${GITHUB_OUTPUT}"
- name: Create a PR comment # - name: Create a PR comment
if: ${{ github.event.pull_request }} # if: ${{ github.event.pull_request }}
uses: thollander/actions-comment-pull-request@v2 # uses: thollander/actions-comment-pull-request@v2
with: # with:
message: | # message: |
## Linux Benchmarks for ${{ github.event.pull_request.head.sha }} # ## Linux Benchmarks for ${{ github.event.pull_request.head.sha }}
${{ steps.benchmark.outputs.markdown }} # ${{ steps.benchmark.outputs.markdown }}
comment_tag: "benchy comment" # comment_tag: "benchy comment"
#
- name: Create a commit comment # - name: Create a commit comment
if: ${{ !github.event.pull_request }} # if: ${{ !github.event.pull_request }}
uses: peter-evans/commit-comment@v2 # uses: peter-evans/commit-comment@v2
with: # with:
body: | # body: |
## Linux Benchmarks # ## Linux Benchmarks
${{ steps.benchmark.outputs.markdown }} # ${{ steps.benchmark.outputs.markdown }}

81
Cargo.lock generated

@ -357,6 +357,19 @@ dependencies = [
"windows-sys 0.48.0", "windows-sys 0.48.0",
] ]
[[package]]
name = "console"
version = "0.15.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c926e00cc70edefdc64d3a5ff31cc65bb97a3460097762bd23afb4d8145fccf8"
dependencies = [
"encode_unicode",
"lazy_static",
"libc",
"unicode-width",
"windows-sys 0.45.0",
]
[[package]] [[package]]
name = "constant_time_eq" name = "constant_time_eq"
version = "0.1.5" version = "0.1.5"
@ -540,6 +553,12 @@ dependencies = [
"winreg", "winreg",
] ]
[[package]]
name = "encode_unicode"
version = "0.3.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f"
[[package]] [[package]]
name = "encoding_rs" name = "encoding_rs"
version = "0.8.32" version = "0.8.32"
@ -638,6 +657,8 @@ dependencies = [
"embed-resource", "embed-resource",
"encoding_rs_io", "encoding_rs_io",
"env_logger", "env_logger",
"http",
"indicatif",
"indoc", "indoc",
"junction", "junction",
"log", "log",
@ -912,6 +933,20 @@ dependencies = [
"hashbrown", "hashbrown",
] ]
[[package]]
name = "indicatif"
version = "0.17.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "763a5a8f45087d6bcea4222e7b72c291a054edf80e4ef6efd2a4979878c7bea3"
dependencies = [
"console",
"instant",
"number_prefix",
"portable-atomic",
"unicode-segmentation",
"unicode-width",
]
[[package]] [[package]]
name = "indoc" name = "indoc"
version = "2.0.2" version = "2.0.2"
@ -1164,6 +1199,12 @@ dependencies = [
"libc", "libc",
] ]
[[package]]
name = "number_prefix"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "830b246a0e5f20af87141b25c173cd1b609bd7779a4617d6ec582abaf90870f3"
[[package]] [[package]]
name = "object" name = "object"
version = "0.30.4" version = "0.30.4"
@ -1248,6 +1289,12 @@ version = "0.3.27"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "26072860ba924cbfa98ea39c8c19b4dd6a4a25423dbdf219c1eca91aa0cf6964" checksum = "26072860ba924cbfa98ea39c8c19b4dd6a4a25423dbdf219c1eca91aa0cf6964"
[[package]]
name = "portable-atomic"
version = "1.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f32154ba0af3a075eefa1eda8bb414ee928f62303a54ea85b8d6638ff1a6ee9e"
[[package]] [[package]]
name = "pretty_assertions" name = "pretty_assertions"
version = "1.4.0" version = "1.4.0"
@ -1904,6 +1951,12 @@ dependencies = [
"tinyvec", "tinyvec",
] ]
[[package]]
name = "unicode-segmentation"
version = "1.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d4c87d22b6e3f4a18d4d40ef354e97c90fcb14dd91d7dc0aa9d8a1172ebf7202"
[[package]] [[package]]
name = "unicode-width" name = "unicode-width"
version = "0.1.10" version = "0.1.10"
@ -2112,7 +2165,7 @@ version = "0.48.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e686886bc078bc1b0b600cac0147aadb815089b6e4da64016cbd754b6342700f" checksum = "e686886bc078bc1b0b600cac0147aadb815089b6e4da64016cbd754b6342700f"
dependencies = [ dependencies = [
"windows-targets", "windows-targets 0.48.1",
] ]
[[package]] [[package]]
@ -2130,13 +2183,37 @@ dependencies = [
"windows_x86_64_msvc 0.42.2", "windows_x86_64_msvc 0.42.2",
] ]
[[package]]
name = "windows-sys"
version = "0.45.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0"
dependencies = [
"windows-targets 0.42.2",
]
[[package]] [[package]]
name = "windows-sys" name = "windows-sys"
version = "0.48.0" version = "0.48.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9"
dependencies = [ dependencies = [
"windows-targets", "windows-targets 0.48.1",
]
[[package]]
name = "windows-targets"
version = "0.42.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071"
dependencies = [
"windows_aarch64_gnullvm 0.42.2",
"windows_aarch64_msvc 0.42.2",
"windows_i686_gnu 0.42.2",
"windows_i686_msvc 0.42.2",
"windows_x86_64_gnu 0.42.2",
"windows_x86_64_gnullvm 0.42.2",
"windows_x86_64_msvc 0.42.2",
] ]
[[package]] [[package]]

2
Cargo.toml

@ -30,11 +30,13 @@ sysinfo = "0.29.3"
thiserror = "1.0.44" thiserror = "1.0.44"
clap_complete = "4.3.1" clap_complete = "4.3.1"
anyhow = "1.0.71" anyhow = "1.0.71"
indicatif = { version = "0.17.8", features = ["improved_unicode"] }
[dev-dependencies] [dev-dependencies]
pretty_assertions = "1.4.0" pretty_assertions = "1.4.0"
duct = "0.13.6" duct = "0.13.6"
test-log = "0.2.12" test-log = "0.2.12"
http = "0.2.9"
[build-dependencies] [build-dependencies]
embed-resource = "1.8.0" embed-resource = "1.8.0"

29
README.md

@ -121,7 +121,7 @@ Where `<SHELL>` can be one of the supported shells:
- `bash` - `bash`
- `zsh` - `zsh`
- `fish` - `fish`
- `powershell` - `power-shell`
Please follow your shell instructions to install them. Please follow your shell instructions to install them.
@ -129,7 +129,10 @@ Please follow your shell instructions to install them.
Environment variables need to be setup before you can start using fnm. Environment variables need to be setup before you can start using fnm.
This is done by evaluating the output of `fnm env`. This is done by evaluating the output of `fnm env`.
To automatically run `fnm use` when a directory contains a `.node-version` or `.nvmrc` file, add the `--use-on-cd` option to your shell setup.
> [!NOTE]
> Check out the [Configuration](./docs/configuration.md) section to enable highly
> recommended features, like automatic version switching.
Adding a `.node-version` to your project is as simple as: Adding a `.node-version` to your project is as simple as:
@ -174,20 +177,24 @@ fnm env --use-on-cd | Out-String | Invoke-Expression
``` ```
- For macOS/Linux, the profile is located at `~/.config/powershell/Microsoft.PowerShell_profile.ps1` - For macOS/Linux, the profile is located at `~/.config/powershell/Microsoft.PowerShell_profile.ps1`
- On Windows, PowerShell comes pre-installed, but there are two versions of it. [Read more about it here](https://learn.microsoft.com/en-us/powershell/scripting/windows-powershell/install/installing-windows-powershell). The profile is located at different places depending on which version you're using: - On Windows to edit your profile you can run this in a PowerShell
- Built in PowerShell (aka "Windows PowerShell"): `~\Documents\WindowsPowerShell\Microsoft.PowerShell_profile.ps1` ```powershell
- The newer, PowerShell >= 7, that's not built in: `~\Documents\PowerShell\Microsoft.PowerShell_profile.ps1` notepad $profile
```
#### Windows Command Prompt aka Batch aka WinCMD #### Windows Command Prompt aka Batch aka WinCMD
fnm is also supported but is not entirely covered. [You can set up a startup script](https://superuser.com/a/144348) and append the following line: fnm is also supported but is not entirely covered. [You can set up a startup script](https://superuser.com/a/144348) and append the following lines:
```batch ```batch
FOR /f "tokens=*" %i IN ('fnm env --use-on-cd') DO CALL %i @echo off
:: for /F will launch a new instance of cmd so we create a guard to prevent an infnite loop
if not defined FNM_AUTORUN_GUARD (
set "FNM_AUTORUN_GUARD=AutorunGuard"
FOR /f "tokens=*" %%z IN ('fnm env --use-on-cd') DO CALL %%z
)
``` ```
If you get the error `i was unexpected at this time`, please make a .cmd file as suggested by the first step in the Usage with Cmder secton add it's path to the `AutoRun` registry key.
#### Usage with Cmder #### Usage with Cmder
Usage is very similar to the normal WinCMD install, apart for a few tweaks to allow being called from the cmder startup script. The example **assumes** that the `CMDER_ROOT` environment variable is **set** to the **root directory** of your Cmder installation. Usage is very similar to the normal WinCMD install, apart for a few tweaks to allow being called from the cmder startup script. The example **assumes** that the `CMDER_ROOT` environment variable is **set** to the **root directory** of your Cmder installation.
@ -210,6 +217,10 @@ call "%CMDER_ROOT%\bin\fnm_init.cmd"
You can replace `%CMDER_ROOT%` with any other convenient path too. You can replace `%CMDER_ROOT%` with any other convenient path too.
## [Configuration](./docs/configuration.md)
[See the available configuration options for an extended configuration documentation](./docs/configuration.md)
## [Usage](./docs/commands.md) ## [Usage](./docs/commands.md)
[See the available commands for an extended usage documentation](./docs/commands.md) [See the available commands for an extended usage documentation](./docs/commands.md)

58
docs/commands.md

@ -22,7 +22,7 @@ Commands:
Options: Options:
--node-dist-mirror <NODE_DIST_MIRROR> --node-dist-mirror <NODE_DIST_MIRROR>
https://nodejs.org/dist/ mirror <https://nodejs.org/dist/> mirror
[env: FNM_NODE_DIST_MIRROR] [env: FNM_NODE_DIST_MIRROR]
[default: https://nodejs.org/dist] [default: https://nodejs.org/dist]
@ -55,7 +55,7 @@ Options:
- recursive: Use the version of Node defined within the current directory and all parent directories - recursive: Use the version of Node defined within the current directory and all parent directories
--corepack-enabled --corepack-enabled
Enable corepack support for each new installation. This will make fnm call `corepack enable` on every Node.js installation. For more information about corepack see https://nodejs.org/api/corepack.html Enable corepack support for each new installation. This will make fnm call `corepack enable` on every Node.js installation. For more information about corepack see <https://nodejs.org/api/corepack.html>
[env: FNM_COREPACK_ENABLED] [env: FNM_COREPACK_ENABLED]
@ -82,7 +82,7 @@ Usage: fnm list-remote [OPTIONS]
Options: Options:
--node-dist-mirror <NODE_DIST_MIRROR> --node-dist-mirror <NODE_DIST_MIRROR>
https://nodejs.org/dist/ mirror <https://nodejs.org/dist/> mirror
[env: FNM_NODE_DIST_MIRROR] [env: FNM_NODE_DIST_MIRROR]
[default: https://nodejs.org/dist] [default: https://nodejs.org/dist]
@ -115,7 +115,7 @@ Options:
- recursive: Use the version of Node defined within the current directory and all parent directories - recursive: Use the version of Node defined within the current directory and all parent directories
--corepack-enabled --corepack-enabled
Enable corepack support for each new installation. This will make fnm call `corepack enable` on every Node.js installation. For more information about corepack see https://nodejs.org/api/corepack.html Enable corepack support for each new installation. This will make fnm call `corepack enable` on every Node.js installation. For more information about corepack see <https://nodejs.org/api/corepack.html>
[env: FNM_COREPACK_ENABLED] [env: FNM_COREPACK_ENABLED]
@ -139,7 +139,7 @@ Usage: fnm list [OPTIONS]
Options: Options:
--node-dist-mirror <NODE_DIST_MIRROR> --node-dist-mirror <NODE_DIST_MIRROR>
https://nodejs.org/dist/ mirror <https://nodejs.org/dist/> mirror
[env: FNM_NODE_DIST_MIRROR] [env: FNM_NODE_DIST_MIRROR]
[default: https://nodejs.org/dist] [default: https://nodejs.org/dist]
@ -172,7 +172,7 @@ Options:
- recursive: Use the version of Node defined within the current directory and all parent directories - recursive: Use the version of Node defined within the current directory and all parent directories
--corepack-enabled --corepack-enabled
Enable corepack support for each new installation. This will make fnm call `corepack enable` on every Node.js installation. For more information about corepack see https://nodejs.org/api/corepack.html Enable corepack support for each new installation. This will make fnm call `corepack enable` on every Node.js installation. For more information about corepack see <https://nodejs.org/api/corepack.html>
[env: FNM_COREPACK_ENABLED] [env: FNM_COREPACK_ENABLED]
@ -203,7 +203,7 @@ Options:
Install latest LTS Install latest LTS
--node-dist-mirror <NODE_DIST_MIRROR> --node-dist-mirror <NODE_DIST_MIRROR>
https://nodejs.org/dist/ mirror <https://nodejs.org/dist/> mirror
[env: FNM_NODE_DIST_MIRROR] [env: FNM_NODE_DIST_MIRROR]
[default: https://nodejs.org/dist] [default: https://nodejs.org/dist]
@ -216,6 +216,12 @@ Options:
--latest --latest
Install latest version Install latest version
--progress <PROGRESS>
Show an interactive progress bar for the download status
[default: auto]
[possible values: auto, never, always]
--log-level <LOG_LEVEL> --log-level <LOG_LEVEL>
The log level of fnm commands The log level of fnm commands
@ -239,7 +245,7 @@ Options:
- recursive: Use the version of Node defined within the current directory and all parent directories - recursive: Use the version of Node defined within the current directory and all parent directories
--corepack-enabled --corepack-enabled
Enable corepack support for each new installation. This will make fnm call `corepack enable` on every Node.js installation. For more information about corepack see https://nodejs.org/api/corepack.html Enable corepack support for each new installation. This will make fnm call `corepack enable` on every Node.js installation. For more information about corepack see <https://nodejs.org/api/corepack.html>
[env: FNM_COREPACK_ENABLED] [env: FNM_COREPACK_ENABLED]
@ -270,7 +276,7 @@ Options:
Install the version if it isn't installed yet Install the version if it isn't installed yet
--node-dist-mirror <NODE_DIST_MIRROR> --node-dist-mirror <NODE_DIST_MIRROR>
https://nodejs.org/dist/ mirror <https://nodejs.org/dist/> mirror
[env: FNM_NODE_DIST_MIRROR] [env: FNM_NODE_DIST_MIRROR]
[default: https://nodejs.org/dist] [default: https://nodejs.org/dist]
@ -306,7 +312,7 @@ Options:
- recursive: Use the version of Node defined within the current directory and all parent directories - recursive: Use the version of Node defined within the current directory and all parent directories
--corepack-enabled --corepack-enabled
Enable corepack support for each new installation. This will make fnm call `corepack enable` on every Node.js installation. For more information about corepack see https://nodejs.org/api/corepack.html Enable corepack support for each new installation. This will make fnm call `corepack enable` on every Node.js installation. For more information about corepack see <https://nodejs.org/api/corepack.html>
[env: FNM_COREPACK_ENABLED] [env: FNM_COREPACK_ENABLED]
@ -334,7 +340,7 @@ Usage: fnm env [OPTIONS]
Options: Options:
--node-dist-mirror <NODE_DIST_MIRROR> --node-dist-mirror <NODE_DIST_MIRROR>
https://nodejs.org/dist/ mirror <https://nodejs.org/dist/> mirror
[env: FNM_NODE_DIST_MIRROR] [env: FNM_NODE_DIST_MIRROR]
[default: https://nodejs.org/dist] [default: https://nodejs.org/dist]
@ -378,7 +384,7 @@ Options:
- recursive: Use the version of Node defined within the current directory and all parent directories - recursive: Use the version of Node defined within the current directory and all parent directories
--corepack-enabled --corepack-enabled
Enable corepack support for each new installation. This will make fnm call `corepack enable` on every Node.js installation. For more information about corepack see https://nodejs.org/api/corepack.html Enable corepack support for each new installation. This will make fnm call `corepack enable` on every Node.js installation. For more information about corepack see <https://nodejs.org/api/corepack.html>
[env: FNM_COREPACK_ENABLED] [env: FNM_COREPACK_ENABLED]
@ -402,7 +408,7 @@ Usage: fnm completions [OPTIONS]
Options: Options:
--node-dist-mirror <NODE_DIST_MIRROR> --node-dist-mirror <NODE_DIST_MIRROR>
https://nodejs.org/dist/ mirror <https://nodejs.org/dist/> mirror
[env: FNM_NODE_DIST_MIRROR] [env: FNM_NODE_DIST_MIRROR]
[default: https://nodejs.org/dist] [default: https://nodejs.org/dist]
@ -440,7 +446,7 @@ Options:
- recursive: Use the version of Node defined within the current directory and all parent directories - recursive: Use the version of Node defined within the current directory and all parent directories
--corepack-enabled --corepack-enabled
Enable corepack support for each new installation. This will make fnm call `corepack enable` on every Node.js installation. For more information about corepack see https://nodejs.org/api/corepack.html Enable corepack support for each new installation. This will make fnm call `corepack enable` on every Node.js installation. For more information about corepack see <https://nodejs.org/api/corepack.html>
[env: FNM_COREPACK_ENABLED] [env: FNM_COREPACK_ENABLED]
@ -471,7 +477,7 @@ Arguments:
Options: Options:
--node-dist-mirror <NODE_DIST_MIRROR> --node-dist-mirror <NODE_DIST_MIRROR>
https://nodejs.org/dist/ mirror <https://nodejs.org/dist/> mirror
[env: FNM_NODE_DIST_MIRROR] [env: FNM_NODE_DIST_MIRROR]
[default: https://nodejs.org/dist] [default: https://nodejs.org/dist]
@ -504,7 +510,7 @@ Options:
- recursive: Use the version of Node defined within the current directory and all parent directories - recursive: Use the version of Node defined within the current directory and all parent directories
--corepack-enabled --corepack-enabled
Enable corepack support for each new installation. This will make fnm call `corepack enable` on every Node.js installation. For more information about corepack see https://nodejs.org/api/corepack.html Enable corepack support for each new installation. This will make fnm call `corepack enable` on every Node.js installation. For more information about corepack see <https://nodejs.org/api/corepack.html>
[env: FNM_COREPACK_ENABLED] [env: FNM_COREPACK_ENABLED]
@ -532,7 +538,7 @@ Arguments:
Options: Options:
--node-dist-mirror <NODE_DIST_MIRROR> --node-dist-mirror <NODE_DIST_MIRROR>
https://nodejs.org/dist/ mirror <https://nodejs.org/dist/> mirror
[env: FNM_NODE_DIST_MIRROR] [env: FNM_NODE_DIST_MIRROR]
[default: https://nodejs.org/dist] [default: https://nodejs.org/dist]
@ -565,7 +571,7 @@ Options:
- recursive: Use the version of Node defined within the current directory and all parent directories - recursive: Use the version of Node defined within the current directory and all parent directories
--corepack-enabled --corepack-enabled
Enable corepack support for each new installation. This will make fnm call `corepack enable` on every Node.js installation. For more information about corepack see https://nodejs.org/api/corepack.html Enable corepack support for each new installation. This will make fnm call `corepack enable` on every Node.js installation. For more information about corepack see <https://nodejs.org/api/corepack.html>
[env: FNM_COREPACK_ENABLED] [env: FNM_COREPACK_ENABLED]
@ -595,7 +601,7 @@ Arguments:
Options: Options:
--node-dist-mirror <NODE_DIST_MIRROR> --node-dist-mirror <NODE_DIST_MIRROR>
https://nodejs.org/dist/ mirror <https://nodejs.org/dist/> mirror
[env: FNM_NODE_DIST_MIRROR] [env: FNM_NODE_DIST_MIRROR]
[default: https://nodejs.org/dist] [default: https://nodejs.org/dist]
@ -628,7 +634,7 @@ Options:
- recursive: Use the version of Node defined within the current directory and all parent directories - recursive: Use the version of Node defined within the current directory and all parent directories
--corepack-enabled --corepack-enabled
Enable corepack support for each new installation. This will make fnm call `corepack enable` on every Node.js installation. For more information about corepack see https://nodejs.org/api/corepack.html Enable corepack support for each new installation. This will make fnm call `corepack enable` on every Node.js installation. For more information about corepack see <https://nodejs.org/api/corepack.html>
[env: FNM_COREPACK_ENABLED] [env: FNM_COREPACK_ENABLED]
@ -652,7 +658,7 @@ Usage: fnm current [OPTIONS]
Options: Options:
--node-dist-mirror <NODE_DIST_MIRROR> --node-dist-mirror <NODE_DIST_MIRROR>
https://nodejs.org/dist/ mirror <https://nodejs.org/dist/> mirror
[env: FNM_NODE_DIST_MIRROR] [env: FNM_NODE_DIST_MIRROR]
[default: https://nodejs.org/dist] [default: https://nodejs.org/dist]
@ -685,7 +691,7 @@ Options:
- recursive: Use the version of Node defined within the current directory and all parent directories - recursive: Use the version of Node defined within the current directory and all parent directories
--corepack-enabled --corepack-enabled
Enable corepack support for each new installation. This will make fnm call `corepack enable` on every Node.js installation. For more information about corepack see https://nodejs.org/api/corepack.html Enable corepack support for each new installation. This will make fnm call `corepack enable` on every Node.js installation. For more information about corepack see <https://nodejs.org/api/corepack.html>
[env: FNM_COREPACK_ENABLED] [env: FNM_COREPACK_ENABLED]
@ -718,7 +724,7 @@ Arguments:
Options: Options:
--node-dist-mirror <NODE_DIST_MIRROR> --node-dist-mirror <NODE_DIST_MIRROR>
https://nodejs.org/dist/ mirror <https://nodejs.org/dist/> mirror
[env: FNM_NODE_DIST_MIRROR] [env: FNM_NODE_DIST_MIRROR]
[default: https://nodejs.org/dist] [default: https://nodejs.org/dist]
@ -754,7 +760,7 @@ Options:
- recursive: Use the version of Node defined within the current directory and all parent directories - recursive: Use the version of Node defined within the current directory and all parent directories
--corepack-enabled --corepack-enabled
Enable corepack support for each new installation. This will make fnm call `corepack enable` on every Node.js installation. For more information about corepack see https://nodejs.org/api/corepack.html Enable corepack support for each new installation. This will make fnm call `corepack enable` on every Node.js installation. For more information about corepack see <https://nodejs.org/api/corepack.html>
[env: FNM_COREPACK_ENABLED] [env: FNM_COREPACK_ENABLED]
@ -784,7 +790,7 @@ Arguments:
Options: Options:
--node-dist-mirror <NODE_DIST_MIRROR> --node-dist-mirror <NODE_DIST_MIRROR>
https://nodejs.org/dist/ mirror <https://nodejs.org/dist/> mirror
[env: FNM_NODE_DIST_MIRROR] [env: FNM_NODE_DIST_MIRROR]
[default: https://nodejs.org/dist] [default: https://nodejs.org/dist]
@ -817,7 +823,7 @@ Options:
- recursive: Use the version of Node defined within the current directory and all parent directories - recursive: Use the version of Node defined within the current directory and all parent directories
--corepack-enabled --corepack-enabled
Enable corepack support for each new installation. This will make fnm call `corepack enable` on every Node.js installation. For more information about corepack see https://nodejs.org/api/corepack.html Enable corepack support for each new installation. This will make fnm call `corepack enable` on every Node.js installation. For more information about corepack see <https://nodejs.org/api/corepack.html>
[env: FNM_COREPACK_ENABLED] [env: FNM_COREPACK_ENABLED]

72
docs/configuration.md

@ -0,0 +1,72 @@
# Configuration
fnm comes with many features out of the box. Some of them are not activated by default as they’re changing your shell default behavior, and some are just a feature flag to avoid breaking changes or just experimental until we decide it is worthwhile to introduce them.
All these features can be configured by adding flags to the `fnm env` call when initializing the shell. For instance, if your shell set up looks like `eval "$(fnm env)"` then you can add a flag to it by changing it to `eval "$(fnm env --my-flag=value)"`
Here’s a list of these features and capabilities:
### `--use-on-cd`
**✅ Highly recommended**
`--use-on-cd` appends output to `fnm env`'s output that will hook into your shell upon changing directories, and will switch the Node.js version based on the requirements of the current directory, based on `.node-version` or `.nvmrc` (or `packages.json#engines#node` if `--resolve-engines` was enabled).
This allows you do avoid thinking about `fnm use`, and only `cd <DIR>` to make it work.
### `--version-file-strategy=recursive`
**✅ Highly recommended**
Makes `fnm use` and `fnm install` take parent directories into account when looking for a version file ("dotfile")--when no argument was given.
So, let's say we have the following directory structure:
```
repo/
├── package.json
├── .node-version <- with content: `20.0.0`
└── packages/
└── my-package/ <- I am here
└── package.json
```
And I'm running the following command:
```sh-session
repo/packages/my-package$ fnm use
```
Then fnm will switch to Node.js v20.0.0.
Without the explicit flag, the value is set to `local`, which will not traverse the directory tree and therefore will print:
```sh-session
repo/packages/my-package$ fnm use
error: Can't find version in dotfiles. Please provide a version manually to the command.
```
### `--enable-corepack`
**🧪 Experimental**
Runs [`corepack enable`](https://nodejs.org/api/corepack.html#enabling-the-feature) when a new version of Node.js is installed. Experimental due to the fact Corepack itself is experimental.
### `--resolve-engines`
**🧪 Experimental**
Treats `package.json#engines#node` as a valid Node.js version file ("dotfile"). So, if you have a package.json with the following content:
```json
{
"engines": {
"node": ">=20 <21"
}
}
```
Then:
- `fnm install` will install the latest satisfying Node.js 20.x version available in the Node.js dist server
- `fnm use` will use the latest satisfying Node.js 20.x version available on your system, or prompt to install if no version matched.

3
rust-toolchain.toml

@ -0,0 +1,3 @@
[toolchain]
channel = "1.78"
components = ["rustfmt", "clippy"]

7
src/arch.rs

@ -4,6 +4,7 @@ use crate::version::Version;
pub enum Arch { pub enum Arch {
X86, X86,
X64, X64,
X64Musl,
Arm64, Arm64,
Armv7l, Armv7l,
Ppc64le, Ppc64le,
@ -16,10 +17,10 @@ pub enum Arch {
pub fn get_safe_arch<'a>(arch: &'a Arch, version: &Version) -> &'a Arch { pub fn get_safe_arch<'a>(arch: &'a Arch, version: &Version) -> &'a Arch {
use crate::system_info::{platform_arch, platform_name}; use crate::system_info::{platform_arch, platform_name};
return match (platform_name(), platform_arch(), version) { match (platform_name(), platform_arch(), version) {
("darwin", "arm64", Version::Semver(v)) if v.major < 16 => &Arch::X64, ("darwin", "arm64", Version::Semver(v)) if v.major < 16 => &Arch::X64,
_ => arch, _ => arch,
}; }
} }
#[cfg(windows)] #[cfg(windows)]
@ -43,6 +44,7 @@ impl std::str::FromStr for Arch {
match s { match s {
"x86" => Ok(Arch::X86), "x86" => Ok(Arch::X86),
"x64" => Ok(Arch::X64), "x64" => Ok(Arch::X64),
"x64-musl" => Ok(Arch::X64Musl),
"arm64" => Ok(Arch::Arm64), "arm64" => Ok(Arch::Arm64),
"armv7l" => Ok(Arch::Armv7l), "armv7l" => Ok(Arch::Armv7l),
"ppc64le" => Ok(Arch::Ppc64le), "ppc64le" => Ok(Arch::Ppc64le),
@ -58,6 +60,7 @@ impl std::fmt::Display for Arch {
let arch_str = match self { let arch_str = match self {
Arch::X86 => String::from("x86"), Arch::X86 => String::from("x86"),
Arch::X64 => String::from("x64"), Arch::X64 => String::from("x64"),
Arch::X64Musl => String::from("x64-musl"),
Arch::Arm64 => String::from("arm64"), Arch::Arm64 => String::from("arm64"),
Arch::Armv7l => String::from("armv7l"), Arch::Armv7l => String::from("armv7l"),
Arch::Ppc64le => String::from("ppc64le"), Arch::Ppc64le => String::from("ppc64le"),

4
src/archive/mod.rs

@ -3,5 +3,9 @@ pub mod tar_xz;
pub mod zip; pub mod zip;
pub use self::extract::{Error, Extract}; pub use self::extract::{Error, Extract};
#[cfg(unix)]
pub use self::tar_xz::TarXz; pub use self::tar_xz::TarXz;
#[cfg(windows)]
pub use self::zip::Zip; pub use self::zip::Zip;

2
src/commands/env.rs

@ -44,7 +44,7 @@ fn make_symlink(config: &FnmConfig) -> Result<std::path::PathBuf, Error> {
} }
match symlink_dir(config.default_version_dir(), &temp_dir) { match symlink_dir(config.default_version_dir(), &temp_dir) {
Ok(_) => Ok(temp_dir), Ok(()) => Ok(temp_dir),
Err(source) => Err(Error::CantCreateSymlink { source, temp_dir }), Err(source) => Err(Error::CantCreateSymlink { source, temp_dir }),
} }
} }

16
src/commands/install.rs

@ -5,6 +5,7 @@ use crate::config::FnmConfig;
use crate::downloader::{install_node_dist, Error as DownloaderError}; use crate::downloader::{install_node_dist, Error as DownloaderError};
use crate::lts::LtsType; use crate::lts::LtsType;
use crate::outln; use crate::outln;
use crate::progress::ProgressConfig;
use crate::remote_node_index; use crate::remote_node_index;
use crate::user_version::UserVersion; use crate::user_version::UserVersion;
use crate::version::Version; use crate::version::Version;
@ -25,6 +26,12 @@ pub struct Install {
/// Install latest version /// Install latest version
#[clap(long, conflicts_with_all = &["version", "lts"])] #[clap(long, conflicts_with_all = &["version", "lts"])]
pub latest: bool, pub latest: bool,
/// Show an interactive progress bar for the download
/// status.
#[clap(long, default_value_t)]
#[arg(value_enum)]
pub progress: ProgressConfig,
} }
impl Install { impl Install {
@ -34,16 +41,19 @@ impl Install {
version: v, version: v,
lts: false, lts: false,
latest: false, latest: false,
..
} => Ok(v), } => Ok(v),
Self { Self {
version: None, version: None,
lts: true, lts: true,
latest: false, latest: false,
..
} => Ok(Some(UserVersion::Full(Version::Lts(LtsType::Latest)))), } => Ok(Some(UserVersion::Full(Version::Lts(LtsType::Latest)))),
Self { Self {
version: None, version: None,
lts: false, lts: false,
latest: true, latest: true,
..
} => Ok(Some(UserVersion::Full(Version::Latest))), } => Ok(Some(UserVersion::Full(Version::Latest))),
_ => Err(Error::TooManyVersionsProvided), _ => Err(Error::TooManyVersionsProvided),
} }
@ -55,6 +65,7 @@ impl Command for Install {
fn apply(self, config: &FnmConfig) -> Result<(), Self::Error> { fn apply(self, config: &FnmConfig) -> Result<(), Self::Error> {
let current_dir = std::env::current_dir().unwrap(); let current_dir = std::env::current_dir().unwrap();
let show_progress = self.progress.enabled(config);
let current_version = self let current_version = self
.version()? .version()?
@ -131,12 +142,13 @@ impl Command for Install {
&config.node_dist_mirror, &config.node_dist_mirror,
config.installations_dir(), config.installations_dir(),
safe_arch, safe_arch,
show_progress,
) { ) {
Err(err @ DownloaderError::VersionAlreadyInstalled { .. }) => { Err(err @ DownloaderError::VersionAlreadyInstalled { .. }) => {
outln!(config, Error, "{} {}", "warning:".bold().yellow(), err); outln!(config, Error, "{} {}", "warning:".bold().yellow(), err);
} }
Err(source) => Err(Error::DownloadError { source })?, Err(source) => Err(Error::DownloadError { source })?,
Ok(_) => {} Ok(()) => {}
}; };
if config.corepack_enabled() { if config.corepack_enabled() {
@ -225,6 +237,7 @@ mod tests {
version: UserVersion::from_str("12.0.0").ok(), version: UserVersion::from_str("12.0.0").ok(),
lts: false, lts: false,
latest: false, latest: false,
progress: ProgressConfig::Never,
} }
.apply(&config) .apply(&config)
.expect("Can't install"); .expect("Can't install");
@ -250,6 +263,7 @@ mod tests {
version: None, version: None,
lts: false, lts: false,
latest: true, latest: true,
progress: ProgressConfig::Never,
} }
.apply(&config) .apply(&config)
.expect("Can't install"); .expect("Can't install");

2
src/commands/use.rs

@ -153,7 +153,7 @@ fn install_new_version(
fn replace_symlink(from: &std::path::Path, to: &std::path::Path) -> std::io::Result<()> { fn replace_symlink(from: &std::path::Path, to: &std::path::Path) -> std::io::Result<()> {
let symlink_deletion_result = fs::remove_symlink_dir(to); let symlink_deletion_result = fs::remove_symlink_dir(to);
match fs::symlink_dir(from, to) { match fs::symlink_dir(from, to) {
ok @ Ok(_) => ok, ok @ Ok(()) => ok,
err @ Err(_) => symlink_deletion_result.and(err), err @ Err(_) => symlink_deletion_result.and(err),
} }
} }

4
src/config.rs

@ -7,7 +7,7 @@ use url::Url;
#[derive(clap::Parser, Debug)] #[derive(clap::Parser, Debug)]
pub struct FnmConfig { pub struct FnmConfig {
/// https://nodejs.org/dist/ mirror /// <https://nodejs.org/dist/> mirror
#[clap( #[clap(
long, long,
env = "FNM_NODE_DIST_MIRROR", env = "FNM_NODE_DIST_MIRROR",
@ -67,7 +67,7 @@ pub struct FnmConfig {
/// Enable corepack support for each new installation. /// Enable corepack support for each new installation.
/// This will make fnm call `corepack enable` on every Node.js installation. /// This will make fnm call `corepack enable` on every Node.js installation.
/// For more information about corepack see https://nodejs.org/api/corepack.html /// For more information about corepack see <https://nodejs.org/api/corepack.html>
#[clap( #[clap(
long, long,
env = "FNM_COREPACK_ENABLED", env = "FNM_COREPACK_ENABLED",

19
src/downloader.rs

@ -2,8 +2,11 @@ use crate::arch::Arch;
use crate::archive; use crate::archive;
use crate::archive::{Error as ExtractError, Extract}; use crate::archive::{Error as ExtractError, Extract};
use crate::directory_portal::DirectoryPortal; use crate::directory_portal::DirectoryPortal;
use crate::progress::ResponseProgress;
use crate::version::Version; use crate::version::Version;
use indicatif::ProgressDrawTarget;
use log::debug; use log::debug;
use std::io::Read;
use std::path::Path; use std::path::Path;
use std::path::PathBuf; use std::path::PathBuf;
use thiserror::Error; use thiserror::Error;
@ -63,10 +66,7 @@ fn download_url(base_url: &Url, version: &Version, arch: &Arch) -> Url {
.unwrap() .unwrap()
} }
pub fn extract_archive_into<P: AsRef<Path>>( fn extract_archive_into(path: impl AsRef<Path>, response: impl Read) -> Result<(), Error> {
path: P,
response: crate::http::Response,
) -> Result<(), Error> {
#[cfg(unix)] #[cfg(unix)]
let extractor = archive::TarXz::new(response); let extractor = archive::TarXz::new(response);
#[cfg(windows)] #[cfg(windows)]
@ -81,6 +81,7 @@ pub fn install_node_dist<P: AsRef<Path>>(
node_dist_mirror: &Url, node_dist_mirror: &Url,
installations_dir: P, installations_dir: P,
arch: &Arch, arch: &Arch,
show_progress: bool,
) -> Result<(), Error> { ) -> Result<(), Error> {
let installation_dir = PathBuf::from(installations_dir.as_ref()).join(version.v_str()); let installation_dir = PathBuf::from(installations_dir.as_ref()).join(version.v_str());
@ -109,7 +110,14 @@ pub fn install_node_dist<P: AsRef<Path>>(
} }
debug!("Extracting response..."); debug!("Extracting response...");
if show_progress {
extract_archive_into(
&portal,
ResponseProgress::new(response, ProgressDrawTarget::stderr()),
)?;
} else {
extract_archive_into(&portal, response)?; extract_archive_into(&portal, response)?;
}
debug!("Extraction completed"); debug!("Extraction completed");
let installed_directory = std::fs::read_dir(&portal)? let installed_directory = std::fs::read_dir(&portal)?
@ -171,7 +179,8 @@ mod tests {
let version = Version::parse("12.0.0").unwrap(); let version = Version::parse("12.0.0").unwrap();
let arch = Arch::X64; let arch = Arch::X64;
let node_dist_mirror = Url::parse("https://nodejs.org/dist/").unwrap(); let node_dist_mirror = Url::parse("https://nodejs.org/dist/").unwrap();
install_node_dist(&version, &node_dist_mirror, path, &arch).expect("Can't install Node 12"); install_node_dist(&version, &node_dist_mirror, path, &arch, false)
.expect("Can't install Node 12");
let mut location_path = path.join(version.v_str()).join("installation"); let mut location_path = path.join(version.v_str()).join("installation");

1
src/main.rs

@ -22,6 +22,7 @@ mod installed_versions;
mod lts; mod lts;
mod package_json; mod package_json;
mod path_ext; mod path_ext;
mod progress;
mod remote_node_index; mod remote_node_index;
mod shell; mod shell;
mod system_info; mod system_info;

165
src/progress.rs

@ -0,0 +1,165 @@
use std::io::Read;
use indicatif::{ProgressBar, ProgressDrawTarget, ProgressStyle};
use reqwest::blocking::Response;
pub struct ResponseProgress {
progress: Option<ProgressBar>,
response: Response,
}
#[derive(Default, Clone, Debug, clap::ValueEnum)]
pub enum ProgressConfig {
#[default]
Auto,
Never,
Always,
}
impl ProgressConfig {
pub fn enabled(&self, config: &crate::config::FnmConfig) -> bool {
match self {
Self::Never => false,
Self::Always => true,
Self::Auto => config
.log_level()
.is_writable(&crate::log_level::LogLevel::Info),
}
}
}
fn make_progress_bar(size: u64, target: ProgressDrawTarget) -> ProgressBar {
let bar = ProgressBar::with_draw_target(Some(size), target);
bar.set_style(
ProgressStyle::with_template(
"{elapsed_precise:.white.dim} {wide_bar:.cyan} {bytes}/{total_bytes} ({bytes_per_sec}, {eta})",
)
.unwrap()
.progress_chars("█▉▊▋▌▍▎▏ "),
);
bar
}
impl ResponseProgress {
pub fn new(response: Response, target: ProgressDrawTarget) -> Self {
Self {
progress: response
.content_length()
.map(|len| make_progress_bar(len, target)),
response,
}
}
pub fn finish(&self) {
if let Some(ref bar) = self.progress {
bar.finish();
}
}
}
impl Read for ResponseProgress {
fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
let size = self.response.read(buf)?;
if let Some(ref bar) = self.progress {
bar.inc(size as u64);
}
Ok(size)
}
}
impl Drop for ResponseProgress {
fn drop(&mut self) {
self.finish();
eprintln!();
}
}
#[cfg(test)]
mod tests {
use indicatif::{ProgressDrawTarget, TermLike};
use reqwest::blocking::Response;
use std::{
io::Read,
sync::{Arc, Mutex},
};
use super::ResponseProgress;
const CONTENT_LENGTH: usize = 100;
#[derive(Debug)]
struct MockedTerm {
pub buf: Arc<Mutex<String>>,
}
impl TermLike for MockedTerm {
fn width(&self) -> u16 {
80
}
fn move_cursor_up(&self, _n: usize) -> std::io::Result<()> {
Ok(())
}
fn move_cursor_down(&self, _n: usize) -> std::io::Result<()> {
Ok(())
}
fn move_cursor_right(&self, _n: usize) -> std::io::Result<()> {
Ok(())
}
fn move_cursor_left(&self, _n: usize) -> std::io::Result<()> {
Ok(())
}
fn write_line(&self, s: &str) -> std::io::Result<()> {
self.buf.lock().unwrap().push_str(s);
Ok(())
}
fn write_str(&self, s: &str) -> std::io::Result<()> {
self.buf.lock().unwrap().push_str(s);
Ok(())
}
fn clear_line(&self) -> std::io::Result<()> {
Ok(())
}
fn flush(&self) -> std::io::Result<()> {
Ok(())
}
}
#[test]
fn test_reads_data_and_shows_progress() {
let response: Response = http::Response::builder()
.header("Content-Length", CONTENT_LENGTH)
.body("a".repeat(CONTENT_LENGTH))
.unwrap()
.into();
let mut buf = [0; CONTENT_LENGTH];
let out_buf = Arc::new(Mutex::new(String::new()));
let mut progress = ResponseProgress::new(
response,
ProgressDrawTarget::term_like(Box::new(MockedTerm {
buf: out_buf.clone(),
})),
);
let size = progress.read(&mut buf[..]).unwrap();
drop(progress);
assert_eq!(size, CONTENT_LENGTH);
assert_eq!(buf, "a".repeat(CONTENT_LENGTH).as_bytes());
assert!(out_buf.lock().unwrap().contains(&"█".repeat(40)));
}
}

6
src/shell/bash.rs

@ -28,13 +28,13 @@ impl Shell for Bash {
fn use_on_cd(&self, config: &crate::config::FnmConfig) -> anyhow::Result<String> { fn use_on_cd(&self, config: &crate::config::FnmConfig) -> anyhow::Result<String> {
let autoload_hook = match config.version_file_strategy() { let autoload_hook = match config.version_file_strategy() {
VersionFileStrategy::Local => indoc!( VersionFileStrategy::Local => indoc!(
r#" r"
if [[ -f .node-version || -f .nvmrc ]]; then if [[ -f .node-version || -f .nvmrc ]]; then
fnm use --silent-if-unchanged fnm use --silent-if-unchanged
fi fi
"# "
), ),
VersionFileStrategy::Recursive => r#"fnm use --silent-if-unchanged"#, VersionFileStrategy::Recursive => r"fnm use --silent-if-unchanged",
}; };
Ok(formatdoc!( Ok(formatdoc!(
r#" r#"

6
src/shell/fish.rs

@ -28,13 +28,13 @@ impl Shell for Fish {
fn use_on_cd(&self, config: &crate::config::FnmConfig) -> anyhow::Result<String> { fn use_on_cd(&self, config: &crate::config::FnmConfig) -> anyhow::Result<String> {
let autoload_hook = match config.version_file_strategy() { let autoload_hook = match config.version_file_strategy() {
VersionFileStrategy::Local => indoc!( VersionFileStrategy::Local => indoc!(
r#" r"
if test -f .node-version -o -f .nvmrc if test -f .node-version -o -f .nvmrc
fnm use --silent-if-unchanged fnm use --silent-if-unchanged
end end
"# "
), ),
VersionFileStrategy::Recursive => r#"fnm use --silent-if-unchanged"#, VersionFileStrategy::Recursive => r"fnm use --silent-if-unchanged",
}; };
Ok(formatdoc!( Ok(formatdoc!(
r#" r#"

2
src/shell/infer/mod.rs

@ -7,7 +7,7 @@ pub use self::unix::infer_shell;
#[cfg(not(unix))] #[cfg(not(unix))]
pub use self::windows::infer_shell; pub use self::windows::infer_shell;
pub(self) fn shell_from_string(shell: &str) -> Option<Box<dyn super::Shell>> { fn shell_from_string(shell: &str) -> Option<Box<dyn super::Shell>> {
use super::{Bash, Fish, PowerShell, WindowsCmd, Zsh}; use super::{Bash, Fish, PowerShell, WindowsCmd, Zsh};
match shell { match shell {
"sh" | "bash" => return Some(Box::from(Bash)), "sh" | "bash" => return Some(Box::from(Bash)),

6
src/shell/powershell.rs

@ -28,11 +28,11 @@ impl Shell for PowerShell {
fn use_on_cd(&self, config: &crate::config::FnmConfig) -> anyhow::Result<String> { fn use_on_cd(&self, config: &crate::config::FnmConfig) -> anyhow::Result<String> {
let autoload_hook = match config.version_file_strategy() { let autoload_hook = match config.version_file_strategy() {
VersionFileStrategy::Local => indoc!( VersionFileStrategy::Local => indoc!(
r#" r"
If ((Test-Path .nvmrc) -Or (Test-Path .node-version)) { & fnm use --silent-if-unchanged } If ((Test-Path .nvmrc) -Or (Test-Path .node-version)) { & fnm use --silent-if-unchanged }
"# "
), ),
VersionFileStrategy::Recursive => r#"fnm use --silent-if-unchanged"#, VersionFileStrategy::Recursive => r"fnm use --silent-if-unchanged",
}; };
Ok(formatdoc!( Ok(formatdoc!(
r#" r#"

6
src/shell/zsh.rs

@ -32,13 +32,13 @@ impl Shell for Zsh {
fn use_on_cd(&self, config: &crate::config::FnmConfig) -> anyhow::Result<String> { fn use_on_cd(&self, config: &crate::config::FnmConfig) -> anyhow::Result<String> {
let autoload_hook = match config.version_file_strategy() { let autoload_hook = match config.version_file_strategy() {
VersionFileStrategy::Local => indoc!( VersionFileStrategy::Local => indoc!(
r#" r"
if [[ -f .node-version || -f .nvmrc ]]; then if [[ -f .node-version || -f .nvmrc ]]; then
fnm use --silent-if-unchanged fnm use --silent-if-unchanged
fi fi
"# "
), ),
VersionFileStrategy::Recursive => r#"fnm use --silent-if-unchanged"#, VersionFileStrategy::Recursive => r"fnm use --silent-if-unchanged",
}; };
Ok(formatdoc!( Ok(formatdoc!(
r#" r#"

Loading…
Cancel
Save