Schniz / fnm

๐Ÿš€ Fast and simple Node.js version manager, built in Rust
https://fnm.vercel.app
GNU General Public License v3.0
17.57k stars 446 forks source link

Nushell support #463

Open Tarnadas opened 3 years ago

Tarnadas commented 3 years ago

Now that nushell has an eval-like mechanism for setting environment variables, Iโ€™d like to open this feature request to also add support for it.

Via the load-env command, one can set multiple environment variables, see here: https://github.com/nushell/nushell/issues/3481

Is that enough to make it possible or is something missing?

Schniz commented 3 years ago

It's enough for basic usage. But if we want --use-on-cd we might want to add functions or whatever it is called in nushell. I really want to try nushell again so it might be a nice experiment ๐Ÿ˜ƒ

orta commented 3 years ago

In the meanwhile - you can get the path edit via:

/home/orta> fnm env | lines | split column '"' | first | get Column2
/tmp/fnm_multishells/10174_1623514914978/bin

Then the rest of the env vars via:

fnm env | str  find-replace -a "export " '' | str find-replace -a '"' '' |  lines | split column = | rename name value | last 5
โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
 # โ”‚         name         โ”‚                   value                    
โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
 0 โ”‚ FNM_MULTISHELL_PATH  โ”‚ "/tmp/fnm_multishells/10210_1623514981704" 
 1 โ”‚ FNM_DIR              โ”‚ "/home/orta/.fnm"                          
 2 โ”‚ FNM_LOGLEVEL         โ”‚ "info"                                     
 3 โ”‚ FNM_NODE_DIST_MIRROR โ”‚ "https://nodejs.org/dist"                  
 4 โ”‚ FNM_ARCH             โ”‚ "x64"                                      
โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€

So to mix it all together:

#Update paths
echo $nu.path | append (fnm env | lines | split column '"' | first | get Column2) | config set_into  path

# Add env vars
fnm env | str  find-replace -a "export " '' | str find-replace -a '"' '' |  lines | split column = | rename name value | last 5 | load-env

And at least from some current messing around it seems to work

ghost commented 3 years ago

Nushell added pathvar command for dynamic path updates. To make work fnm in nushell, add these two commands in the startup:

#load env variables
fnm env --shell bash | lines | last 5 | str find-replace 'export ' '' | str find-replace -a '"' '' | split column = | rename name value | load-env

#add dynamic fnm path
pathvar add $nu.env.FNM_MULTISHELL_PATH

Info: Requires nushell v0.34+ (pathvar command is supported only with v0.34 and above)

fellnerse commented 2 years ago

I had to change the path like this: pathvar add ($nu.env.FNM_MULTISHELL_PATH + /bin)

Southclaws commented 2 years ago

Nushell has from json so a quick easy way to support this and other experimental shells would be to expose environment variables as a simple JSON object.

I made a super quick Go program for this: https://github.com/Southclaws/fnm-nushell

Install with go install github.com/Southclaws/fnm-nushell@latest then run a short pipeline: fnm env --shell powershell | fnm-nushell | from json | load-env in your nushell config.

greym0uth commented 2 years ago

This is the code I added to my env.nu using nushell 0.61.0 that works:

#load env variables
load-env (fnm env --shell bash | lines | str replace 'export ' '' | str replace -a '"' '' | split column = | rename name value | where name != "FNM_ARCH" && name != "PATH" | reduce -f {} {|it, acc| $acc | upsert $it.name $it.value })

#add dynamic fnm path
let-env PATH = $"($env.FNM_MULTISHELL_PATH)/bin:($env.PATH)"
dex157 commented 2 years ago

This is my first experience with nu, so it needs to be tested, but it seems to work:

# FNM
# load env variables
load-env (fnm env --shell bash | lines | str replace 'export ' '' | str replace -a '"' '' | split column = | rename name value | where name != "FNM_ARCH" && name != "PATH" | reduce -f {} {|it, acc| $acc | upsert $it.name $it.value })

# add dynamic fnm path
let-env PATH = ($env.PATH | prepend $"($env.FNM_MULTISHELL_PATH)/bin")

# add fnm with cd
def-env fnmcd [path: string] {
  let-env PWD = ($path | path expand)
  if (['.node-version' '.nvmrc'] | any? ($env.PWD | path join $it | path exists)) {
     fnm use --silent-if-unchanged
  }
}

alias cd = fnmcd
tuscen commented 2 years ago

Could you at least add something like fnm env --json with outputting only fnm related variables there (without modified PATH that is)? In that case we could maintain fnm integration ourselves since nushell is constantly changing. I myself don't really need on-cd functionality and am using fnm as simple nodejs sdk manager.

jcoppis commented 2 years ago

This is the code I added to my env.nu using nushell 0.64.0 that works on WINDOWS:

# FNM for windows
# load env variables
load-env (fnm env --shell cmd | lines | str replace 'SET ' '' | split column = | rename name value | where name != "FNM_ARCH" && name != "PATH" | reduce -f {} {|it, acc| $acc | upsert $it.name $it.value })

# add dynamic fnm path
let-env Path = ($env.Path | prepend $env.FNM_MULTISHELL_PATH)
azzamsa commented 2 years ago

Nu has changed a lot. It is better now. Any plan for fnm to have built-in support for Nushell?

Current workaround inspired by https://github.com/Schniz/fnm/issues/463#issuecomment-1106868402: Nu 0.67.0, GNU/Linux.

# FNM config

# Parse FNM env from other supported shell. It result should looks like this:
# โ”‚ FNM_VERSION_FILE_STRATEGY โ”‚ local                          โ”‚
# โ”‚ FNM_DIR                   โ”‚ /home/user/.fnm                |
# Then load these value key pair to Nushell env
load-env (fnm env --shell bash | lines | str replace 'export ' '' | str replace -a '"' '' | split column = | rename name value | where name != "FNM_ARCH" && name != "PATH" | reduce -f {} {|it, acc| $acc | upsert $it.name $it.value })

# Add dynamic FNM path
let-env PATH = ($env.PATH | append [
  $"($env.FNM_MULTISHELL_PATH)/bin"
])
vajdagabor commented 2 years ago

It seems nushell support is on the way: #801

For the meantime the solution from @azzamsa works for me, but with prepend in the PATH setting.

Nushell has very handy hook feature that can be used to pick node version on directory change. I added this to my config.nu:

let-env config = {
  โ€ฆ
  hooks: {
    env_change: {
      PWD: [{|before, after|
        if ([.nvmrc .node-version] | path exists | any? ($it == true)) {
          fnm use
        }
      }]
    }
  }
  โ€ฆ
}
remmycat commented 1 year ago

Now that #800 landed in 1.32.0, the setup for nu already got much nicer using the fnm env --json command ๐ŸŽ‰

The PR mentions this script:

fnm env --json | from json | load-env
let-env PATH = ($env.PATH | prepend ($env.FNM_MULTISHELL_PATH + "/bin"))

Personally I use this:

^fnm env --json | from json | load-env
let-env PATH = ([([$env.FNM_MULTISHELL_PATH "bin"] | path join) $env.PATH] | str join (char esep))

I concatenate the paths as strings, because this is part of my env.nu where the $env.PATH is not yet parsed as a nu list unless one manually did so. (And I like to rely on path join for adding to a path)

Fishrock123 commented 1 year ago

Ok so, this is currently in my env.nu, and seems to at least not have nushell 0.71 errors

# FNM
# load env variables
fnm env --json | from json | load-env

# add dynamic fnm path
let-env PATH = ($env.PATH | split row (char esep) | prepend ([$env.FNM_MULTISHELL_PATH "bin"] | path join))

# add fnm with cd
def-env fnmcd [path: string] {
  let-env PWD = ($path | path expand)
  if (['.node-version' '.nvmrc'] | any ($env.PWD | path join $it | path exists)) {
     fnm use --silent-if-unchanged
  }
}

alias cd = fnmcd
lirc571 commented 1 year ago

Ok so, this is currently in my env.nu, and seems to at least not have nushell 0.71 errors

# FNM
# load env variables
fnm env --json | from json | load-env

# add dynamic fnm path
let-env PATH = ($env.PATH | split row (char esep) | prepend ([$env.FNM_MULTISHELL_PATH "bin"] | path join))

# add fnm with cd
def-env fnmcd [path: string] {
  let-env PWD = ($path | path expand)
  if (['.node-version' '.nvmrc'] | any ($env.PWD | path join $it | path exists)) {
     fnm use --silent-if-unchanged
  }
}

alias cd = fnmcd

Updated the snippet to work under 0.72:

# FNM
# load env variables
fnm env --json | from json | load-env

# add dynamic fnm path
let-env PATH = ($env.PATH | split row (char esep) | prepend ([$env.FNM_MULTISHELL_PATH "bin"] | path join))

# add fnm with cd
def-env fnmcd [path?: string] {
  if ($path == null) {
    cd
  } else {
    cd ($path | path expand)
  }
  if (['.node-version' '.nvmrc'] | any ($env.PWD | path join $it | path exists)) {
     fnm use --silent-if-unchanged
  }
}

alias cd = fnmcd
ManneWas commented 1 year ago

Ok so, this is currently in my env.nu, and seems to at least not have nushell 0.71 errors

# FNM
# load env variables
fnm env --json | from json | load-env

# add dynamic fnm path
let-env PATH = ($env.PATH | split row (char esep) | prepend ([$env.FNM_MULTISHELL_PATH "bin"] | path join))

# add fnm with cd
def-env fnmcd [path: string] {
  let-env PWD = ($path | path expand)
  if (['.node-version' '.nvmrc'] | any ($env.PWD | path join $it | path exists)) {
     fnm use --silent-if-unchanged
  }
}

alias cd = fnmcd

Updated the snippet to work under 0.72:

# FNM
# load env variables
fnm env --json | from json | load-env

# add dynamic fnm path
let-env PATH = ($env.PATH | split row (char esep) | prepend ([$env.FNM_MULTISHELL_PATH "bin"] | path join))

# add fnm with cd
def-env fnmcd [path?: string] {
  if ($path == null) {
    cd
  } else {
    cd ($path | path expand)
  }
  if (['.node-version' '.nvmrc'] | any ($env.PWD | path join $it | path exists)) {
     fnm use --silent-if-unchanged
  }
}

alias cd = fnmcd

Thank you for this, saved me a ton of time. Had to make these small changes for it to work for me on windows, but I can't say if it's correct or not confidently.

# FNM
# load env variables
fnm env --json | from json | load-env

# add dynamic fnm path
let-env Path = ($env.Path | split row (char esep) | prepend ([$env.FNM_MULTISHELL_PATH] | path join))

# add fnm with cd
def-env fnmcd [path?: string] {
  if ($path == null) {
    cd
  } else {
    cd ($path | path expand)
  }
  if (['.node-version' '.nvmrc'] | any ($env.PWD | path join $it | path exists)) {
     fnm use --silent-if-unchanged
  }
}

alias cd = fnmcd
lirc571 commented 1 year ago

Update for nushell v0.73:

It seems that $it has been removed for any, so any ($env.PWD | path join $it | path exists) needs to be changed to any {|it| $env.PWD | path join $it | path exists}.

We might be using too many unstable features of nushell in the script :joy:

Southclaws commented 1 year ago

That's why I simply wrote it in Go instead ๐Ÿ˜‚

Fishrock123 commented 1 year ago

Full fnm script for nu 0.73

# FNM
# load env variables
fnm env --json | from json | load-env

# add dynamic fnm path
let-env PATH = ($env.PATH | split row (char esep) | prepend ([$env.FNM_MULTISHELL_PATH "bin"] | path join))

# add fnm with cd
def-env fnmcd [path?: string] {
  if ($path == null) {
    cd
  } else {
    cd ($path | path expand)
  }
  if (['.node-version' '.nvmrc'] | any {|it| $env.PWD | path join $it | path exists}) {
     fnm use --silent-if-unchanged
  }
}

alias cd = fnmcd
sr-mothership commented 1 year ago

On mac I'm getting

 65 โ”‚ # load env variables
 66 โ”‚ fnm env --json | from json | load-env
    ยท โ”€โ”ฌโ”€
    ยท  โ•ฐโ”€โ”€ did you mean 'fnmcd'?
 67 โ”‚
    โ•ฐโ”€โ”€โ”€โ”€
  help: No such file or directory (os error 2)

How can I solve this? :D

lirc572 commented 1 year ago

@sr-mothership You need to have fnm in your PATH first. Add something like let-env PATH = ($env.PATH | split row (char esep) | prepend '/home/ubuntu/.cargo/bin') (change the path to the directory of your fnm binary) before the snippet given above.

sr-mothership commented 1 year ago

@sr-mothership You need to have fnm in your PATH first. Add something like let-env PATH = ($env.PATH | split row (char esep) | prepend '/home/ubuntu/.cargo/bin') (change the path to the directory of your fnm binary) before the snippet given above.

That is so weird, since I already have let-env PATH = ($env.PATH |split row ":"| prepend $"($env.HOME)/.cargo/bin") in my config.nu ๐Ÿค”

lirc572 commented 1 year ago

@sr-mothership It should be in env.nu instead. Both the let-env command and the snippet above.

sr-mothership commented 1 year ago

@sr-mothership It should be in env.nu instead. Both the let-env command and the snippet above.

Damn that solved it, haha. Thanks ๐Ÿ™‚

JadoJodo commented 1 year ago

In Nushell 0.77, there is a breaking change related to the use of alias. If you try to use the snippet above, it will break on aliasing cd to the snippet. You can simply rename alias to old-alias, for the time being.

VuiMuich commented 1 year ago

It seems nushell support is on the way: #801

For the meantime the solution from @azzamsa works for me, but with prepend in the PATH setting.

Nushell has very handy hook feature that can be used to pick node version on directory change. I added this to my config.nu:

let-env config = {
  โ€ฆ
  hooks: {
    env_change: {
      PWD: [{|before, after|
        if ([.nvmrc .node-version] | path exists | any? ($it == true)) {
          fnm use
        }
      }]
    }
  }
  โ€ฆ
}

For nu 0.79.1 I had to change the hook a little bit:

    env_change: {
      PWD: [{|before, after|
        if ([.nvmrc .node-version] | path exists | any {|it| ($it == true)}) {
          fnm use
        }
      }]
    }
flying-sheep commented 1 year ago

So to summarize:

  1. add to env.nu:

    if not (which fnm | is-empty) {
      ^fnm env --json | from json | load-env
      let-env PATH = ($env.PATH | prepend [
        $"($env.FNM_MULTISHELL_PATH)/bin"
      ])
    }
  2. add to config.nu:

    let-env config = {
      [โ€ฆ]
      hooks: {
        [โ€ฆ]
        env_change: {
          PWD: [{ |before, after|
            if ('FNM_DIR' in $env) and ([.nvmrc .node-version] | path exists | any { |it| $it }) {
              fnm use
            }
          }]
        }
        [โ€ฆ]
      }
      [โ€ฆ]
    }

If you have fnm on every machine, you can leave out the not (which fnm | is-empty) and 'FNM_DIR' in $env checks.

hustcer commented 1 year ago

For fnm 1.3.5 and nu 0.83:

# env.nu
if not (which fnm | is-empty) {
  ^fnm env --json | from json | load-env
  # Checking `Path` for Windows
  let path = if 'Path' in $env { $env.Path } else { $env.PATH }
  let node_path = if (sys).host.name == 'Windows' {
    $"($env.FNM_MULTISHELL_PATH)"
  } else {
    $"($env.FNM_MULTISHELL_PATH)/bin"
  }
  $env.PATH = ($path | prepend [ $node_path ])
}

# config.nu
$env.config = {
   hooks: {
    env_change: {
      PWD: [{ |before, after|
        if ('FNM_DIR' in $env) and ([.nvmrc .node-version] | path exists | any { |it| $it }) {
          fnm use
        }
      }]
    }
  }
}
marcelarie commented 1 year ago

For fnm 1.3.5 and nu 0.83:

@hustcer your solution works for me, but I always get this warning:


warning: The current Node.js path is not on your PATH environment variable.
You should setup your shell profile to evaluate `fnm env`, see https://github.com/Schniz/fnm#shell-setup on how to do this
Check out our documentation for more information: https://fnm.vercel.app```
hustcer commented 1 year ago

@marcelarie It works for me, However, You can give this PR a try: https://github.com/nushell/nu_scripts/pull/593/files

Borber commented 12 months ago

use this: above v0.85 nushell
anywhere: fnm.nu

load-env (fnm env --shell bash | lines | str replace 'export ' '' | str replace -a '"' '' | split column = | rename name value | where name != "FNM_ARCH" and name != "PATH" | reduce -f {} {|it, acc| $acc | upsert $it.name $it.value })

# Add dynamic FNM path
$env.Path = ($env.Path | append [
  $env.FNM_MULTISHELL_PATH
])

then source it in $nu.config-path

source  /path/to/fnm.nu
jqhr commented 10 months ago

use this: above v0.85 nushell anywhere: fnm.nu

load-env (fnm env --shell bash | lines | str replace 'export ' '' | str replace -a '"' '' | split column = | rename name value | where name != "FNM_ARCH" and name != "PATH" | reduce -f {} {|it, acc| $acc | upsert $it.name $it.value })

# Add dynamic FNM path
$env.Path = ($env.Path | append [
  $env.FNM_MULTISHELL_PATH
])

then source it in $nu.config-path

source  /path/to/fnm.nu

Work for me.

But how to config for fnm use on cd?

marcelarie commented 10 months ago

For me, this works like a charm:

$env.config = {
   hooks: {
    env_change: {
      PWD: [{ |before, after|
        let is_node_dir = [.nvmrc .node-version] | path exists | any { |it| $it }
        if ('FNM_DIR' in $env) and $is_node_dir {
          fnm use # Personally I prefer to use fnm --log-level=quiet use 
        }
      }]
    }
  }
}

if not (which fnm | is-empty) {
  ^fnm env --json | from json | load-env
  # Checking `Path` for Windows
  let path = if 'Path' in $env { $env.Path } else { $env.PATH }
  let node_path = if (sys).host.name == 'Windows' {
    $"($env.FNM_MULTISHELL_PATH)"
  } else {
    $"($env.FNM_MULTISHELL_PATH)/bin"
  }
  $env.PATH = ($path | prepend [ $node_path ])
}
connorjs commented 10 months ago

A slight tweak to the if not block from @marcelarie that I needed to change for my set up (on Windows).

if not (which fnm | is-empty) {
  ^fnm env --json | from json | load-env

  let node_path = if (sys).host.name == 'Windows' {
    $"($env.FNM_MULTISHELL_PATH)"
  } else {
    $"($env.FNM_MULTISHELL_PATH)/bin"
  }

  # Checking `Path` for Windows
  if 'Path' in $env {
    $env.Path = ($env.Path | split row (char esp) | prepend $node_path)
  } else {
    $env.PATH = ($env.PATH | split row (char esp) | prepend $node_path)
  }
}

Of course, I only tried the Windows side. (And we could probably combine the two if-else blocks together.)

Edit: I added the split row bit from the path configuration nushell docs

FrancescElies commented 6 months ago

This could be simplified a bit further, no need for windows patch check in the end.

# File: config.nu
use std "path add"
if not (which fnm | is-empty) {
  ^fnm env --json | from json | load-env
  let node_path = match $nu.os-info.name {
    "windows" => $"($env.FNM_MULTISHELL_PATH)",
    _ => $"($env.FNM_MULTISHELL_PATH)/bin",
  }
  path add $node_path
}
gclarkjr5 commented 6 months ago

Another tweak to what @marcelarie provided. It seems that the solution was somehow conflicting with my existing config as the banner returned after having been set to False and all of my other custom hooks were no longer taking affect. So I went with this.

export-env {
  $env.config = ($env.config | upsert hooks {
      env_change: {
          PWD: ($env.config.hooks.env_change.PWD ++
            [{
              condition: {|before, after| [.nvmrc .node-version] | path exists | any { |it| $it }}
          code: {|before, after|
                if ('FNM_DIR' in $env) {
              fnm use # Personally I prefer to use fnm --log-level=quiet use 
        }
          }
      }]
        )
      }
  })
}

if not (which fnm | is-empty) {
  ^fnm env --json | from json | load-env
  # Checking `Path` for Windows
  let path = if 'Path' in $env { $env.Path } else { $env.PATH }
  let node_path = if (sys).host.name == 'Windows' {
    $"($env.FNM_MULTISHELL_PATH)"
  } else {
    $"($env.FNM_MULTISHELL_PATH)/bin"
  }
  $env.PATH = ($path | prepend [ $node_path ])
}
AbhinavRobinson commented 3 months ago

@gclarkjr5 improvement - sys is deprecated. (sys).host.name can be updated to (sys host).name

kuchta commented 3 months ago
if not (which fnm | is-empty) {
  ^fnm env --resolve-engines --corepack-enabled --json | from json | load-env
  let node_path = match $nu.os-info.name {
    "windows" => $"($env.FNM_MULTISHELL_PATH)",
    _ => $"($env.FNM_MULTISHELL_PATH)/bin",
  }
  $env.PATH = ($env.PATH | prepend [ $node_path ])
}

$env.config = ($env.config | merge {
    hooks: {
        env_change: {
            PWD: [{
                if ([.nvmrc .node-version] | path exists | any { |it| $it }) {
                    fnm use
                } else {
                    fnm --log-level=quiet use default
                }
            }]
        }
    }
})