Tools/fnm.ps1

# Companion for fnm — Fast Node Manager with --use-on-cd auto-switching.
#
# `fnm env --use-on-cd` defines a global `Set-LocationWithFnm` wrapper and rebinds
# `cd` to it (`Set-Alias -Option AllScope -Scope global cd Set-LocationWithFnm`).
# That wrapper calls plain `Set-Location`, so on its own it REPLACES zoxide's smart
# `cd` (zoxide emits `Set-Alias cd __zoxide_z`) and the directory-jump is lost.
#
# To let both coexist:
# 1. Capture whatever currently owns `cd` (zoxide's __zoxide_z if zoxide ran
# first, else Set-Location) into $global:cdBeforeFnm.
# 2. Let fnm install its env + Set-LocationWithFnm/cd hook.
# 3. Re-point Set-LocationWithFnm at the captured command so `cd` performs the
# zoxide jump AND fnm's per-directory Node switch.
#
# Step 1 only recovers zoxide's binding if zoxide registered first, so fnm.json
# declares "dependsOn": ["zoxide"] and Register-DFTool topo-sorts zoxide ahead of
# fnm. With zoxide absent, $global:cdBeforeFnm falls back to Set-Location and fnm
# still works standalone.
#
# Invoke-Expression is required by fnm's init pattern — no alternative exists.
# $global:cdBeforeFnm must be global: fnm's `cd` alias resolves Set-LocationWithFnm
# in the global scope at call time, and that function reads the captured command
# from there — a narrower scope would be invisible to it.
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidUsingInvokeExpression', '')]
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidGlobalVars', '')]
param()

# 1. Capture the current `cd` target before fnm overwrites it. zoxide binds `cd`
# as an alias, so .ReferencedCommand yields the callable __zoxide_z function.
$global:cdBeforeFnm = (Get-Alias 'cd' -Scope Global -ErrorAction Ignore).ReferencedCommand
if ($null -eq $global:cdBeforeFnm) { $global:cdBeforeFnm = Get-Command Set-Location }

# 2. Install fnm's environment and its --use-on-cd hook (defines Set-FnmOnLoad,
# Set-LocationWithFnm, and rebinds the `cd` alias).
fnm env --use-on-cd --shell powershell | Out-String | Invoke-Expression

# 3. Chain fnm's wrapper back through the captured `cd`. fnm's `cd` alias resolves
# Set-LocationWithFnm by name, so redefining the function is enough — no need to
# touch the alias. Forward @args (not fnm's single $path) so zoxide's multi-
# keyword queries (`cd foo bar`) and `cd -`/`cd +` survive intact.
function global:Set-LocationWithFnm {
    if ($args.Count -eq 0) { & $global:cdBeforeFnm } else { & $global:cdBeforeFnm @args }
    Set-FnmOnLoad
}