Last active
January 30, 2026 23:02
-
-
Save euaaron/8b0a2497244b3711e65ad798bdc5873f to your computer and use it in GitHub Desktop.
Windows CLI - Powershell script to download and switch between different Nodejs versions.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # -------------------------------------------- | |
| # Nodejs Version Manager Powershell (nmp) | |
| # v2.0.0 | |
| # Author: Aaron Carneiro <@euaaron> | |
| # Email: nmp@aaroncarneiro.com | |
| # GitHub: https://github.com/euaaron | |
| # This File: https://gist.github.com/euaaron/8b0a2497244b3711e65ad798bdc5873f | |
| # -------------------------------------------- | |
| $nmpVersion = "v2.0.0" | |
| $silentInit = $true | |
| # Configuration | |
| $configFileName = ".nmprc" | |
| $nmprc = "$PSScriptRoot\$configFileName" | |
| # ============ UTILITY FUNCTIONS ============ | |
| # Logging function - centralized console output | |
| function Write-Log { | |
| param( | |
| [string] $Message = "", | |
| [string] $ForegroundColor = "White", | |
| [switch] $Error | |
| ) | |
| if ($Error) { | |
| Write-Host $Message -ForegroundColor Red | |
| } else { | |
| Write-Host $Message -ForegroundColor $ForegroundColor | |
| } | |
| } | |
| # Load configuration from JSON file | |
| function Get-Config { | |
| if (Test-Path $nmprc) { | |
| return Get-Content $nmprc | ConvertFrom-Json | |
| } | |
| # Return default config if file doesn't exist | |
| return @{ | |
| nodeDirectory = $HOME + "\.nmp\node\" | |
| defaultVersion = "24" | |
| architecture = "win-x64" | |
| } | |
| } | |
| # Save configuration to JSON file | |
| function Set-Config { | |
| param( | |
| [PSCustomObject] $Config | |
| ) | |
| try { | |
| $Config | ConvertTo-Json | Set-Content -Path $nmprc -Force | |
| Write-Log "Configuration saved to $nmprc" -ForegroundColor Green | |
| } | |
| catch { | |
| Write-Log "Error saving configuration: $_" -Error | |
| } | |
| } | |
| # Initialize configuration file with defaults if missing | |
| function Initialize-Config { | |
| if (-Not (Test-Path $nmprc)) { | |
| Write-Log "Initializing nmp configuration..." -ForegroundColor Cyan | |
| # Determine system architecture | |
| $systemArch = (Get-CimInstance Win32_OperatingSystem).OSArchitecture | |
| $architecture = switch -Regex ($systemArch) { | |
| "64.*bit|x64|AMD64" { "win-x64" } | |
| "32.*bit|x86" { "win-x86" } | |
| "ARM.*64|aarch64" { "win-arm64" } | |
| default { "win-x64" } | |
| } | |
| Write-Log "Detected architecture: $architecture" -ForegroundColor Green | |
| # Fetch LTS version from nodejs.org | |
| Write-Log "Fetching current Node.js LTS version..." -ForegroundColor Cyan | |
| try { | |
| $indexJson = Invoke-RestMethod -Uri "https://nodejs.org/dist/index.json" -UseBasicParsing | |
| $ltsVersion = ($indexJson | Where-Object { $_.lts -ne $false } | Select-Object -First 1).version | |
| $defaultVersion = $ltsVersion -replace "v(\d+)\..*", '$1' | |
| Write-Log "Current LTS version: v$defaultVersion" -ForegroundColor Green | |
| } | |
| catch { | |
| Write-Log "Warning: Unable to fetch LTS version from nodejs.org. Using fallback version 24" -ForegroundColor Yellow | |
| $defaultVersion = "24" | |
| } | |
| # Prompt user for node directory | |
| $defaultPath = "$HOME\.nmp" | |
| Write-Log "" | |
| Write-Log "Please provide the desired Node.js installation directory." -ForegroundColor Cyan | |
| Write-Log "Default path: $defaultPath (or ~/.nmp)" -ForegroundColor Gray | |
| Write-Host -NoNewline "Enter path (press Enter for default): " | |
| $userInput = Read-Host | |
| if ([string]::IsNullOrWhiteSpace($userInput)) { | |
| $nodeDirectory = $defaultPath + "\node\" | |
| Write-Log "Using default path: $nodeDirectory" -ForegroundColor Green | |
| } | |
| else { | |
| # Clean up user input and ensure it ends properly | |
| $userInput = $userInput.Trim().TrimEnd('\') | |
| $nodeDirectory = $userInput + "\node\" | |
| Write-Log "Using custom path: $nodeDirectory" -ForegroundColor Green | |
| } | |
| $defaultConfig = @{ | |
| nodeDirectory = $nodeDirectory | |
| defaultVersion = $defaultVersion | |
| architecture = $architecture | |
| } | |
| Set-Config $defaultConfig | |
| Write-Log "Configuration file created at $nmprc" -ForegroundColor Green | |
| } | |
| } | |
| function CreateDir { | |
| param( | |
| [string] $Dir = "" | |
| ) | |
| if (-Not $Dir -Contains "*:\") { | |
| $tempDir = $Dir; | |
| $Dir = ([System.Environment]::CurrentDirectory) | |
| $Dir += "\" | |
| $Dir += $tempDir; | |
| } | |
| if (-Not (Test-Path $Dir)) { | |
| $dirPath = $Dir.Split("\"); | |
| $currentPath = "" | |
| $count = 0; | |
| $dirPath | ForEach { | |
| if (-Not ($dirPath[$count] -Eq "") -Or -Not ($dirPath[$count] -Eq "\")) { | |
| $currentPath += $dirPath[$count] | |
| $currentPath += "\" | |
| $currentPath = $currentPath.Replace("\\", "\"); | |
| if (-Not (Test-Path $currentPath) -And -Not ($currentPath -Eq "")) { | |
| mkdir $currentPath | Out-Null | |
| } | |
| } | |
| if (-Not ($dirPath.Length - 1 -Eq $count)) { | |
| $count += 1; | |
| } else { | |
| Write-Log "Directory '$currentPath' has been created successfully!" | |
| return; | |
| } | |
| } | |
| } else { | |
| Write-Log "Error! '$dir' already exists!" -Error | |
| } | |
| } | |
| Function Test-CommandExists { | |
| Param ( | |
| [string] $Command, | |
| [switch] $Silent | |
| ) | |
| $oldPreference = $ErrorActionPreference | |
| $ErrorActionPreference = 'stop' | |
| $exists = $false; | |
| try { | |
| if(Get-Command $Command) { | |
| $exists = $true | |
| if (-Not $Silent) { | |
| Write-Log "Command '$Command' exists!" | |
| } | |
| } | |
| } | |
| Catch { | |
| $exists = $false | |
| if (-Not $Silent) { | |
| Write-Log "Command '$Command' was not found!" -Error | |
| } | |
| } | |
| $ErrorActionPreference = $oldPreference | |
| return $exists; | |
| } | |
| # Ensure directory exists, create if needed | |
| function Ensure-Directory { | |
| param( | |
| [string] $Path | |
| ) | |
| if (-Not (Test-Path $Path)) { | |
| CreateDir -Dir $Path | |
| } | |
| } | |
| # Add or update PATH entry for Node directory | |
| function Set-NodePathEntry { | |
| param( | |
| [string] $NodeDirectory | |
| ) | |
| if ($env:Path -notlike "*$NodeDirectory*") { | |
| $env:Path = "$NodeDirectory;" + $env:Path | |
| } else { | |
| # Remove old entry and add new one at the beginning | |
| $env:Path = ($env:Path -split ';' | Where-Object { $_ -notlike "*node*" }) -join ';' | |
| $env:Path = "$NodeDirectory;" + $env:Path | |
| } | |
| } | |
| # ============ MAIN NMP FUNCTION ============ | |
| function nmp { | |
| param( | |
| [string] $Change = "", | |
| [string] $Arch = "", | |
| [switch] $Force, | |
| [switch] $Version, | |
| [switch] $List, | |
| [switch] $Remote, | |
| [switch] $Help, | |
| [switch] $Init, | |
| [switch] $SetDir, | |
| [switch] $SetDefault, | |
| [string] $Dir = "", | |
| [string] $Default = "" | |
| ) | |
| # Load configuration | |
| $config = Get-Config | |
| # Override config with parameters if provided | |
| if (-Not [string]::IsNullOrEmpty($Dir)) { | |
| $config.nodeDirectory = $Dir | |
| Set-Config $config | |
| } | |
| if (-Not [string]::IsNullOrEmpty($Default)) { | |
| $config.defaultVersion = $Default | |
| Set-Config $config | |
| } | |
| if (-Not [string]::IsNullOrEmpty($Arch)) { | |
| $config.architecture = $Arch | |
| Set-Config $config | |
| } | |
| $nodeDir = $config.nodeDirectory | |
| $defaultVersion = $config.defaultVersion | |
| $arch = if ([string]::IsNullOrEmpty($Arch)) { $config.architecture } else { $Arch } | |
| # -------- INNER FUNCTIONS -------- | |
| function nInit { | |
| Ensure-Directory -Path $nodeDir | |
| $defaultNodeDir = Join-Path $nodeDir $defaultVersion | |
| if (-Not (Test-Path $defaultNodeDir)) { | |
| Write-Log "Default Node version $defaultVersion not found. Installing..." -ForegroundColor Yellow | |
| try { | |
| nChange -Version $defaultVersion -Arch $arch -Force $false | |
| } | |
| catch { | |
| Write-Log "Error installing Node version $defaultVersion : $_" -Error | |
| } | |
| } | |
| if ((Test-Path $defaultNodeDir) -And (Test-Path "$defaultNodeDir\node.exe")) { | |
| Set-NodePathEntry -NodeDirectory $defaultNodeDir | |
| } | |
| } | |
| function nChange { | |
| param ( | |
| [string] $Version = '', | |
| [string] $Arch = "win-x64", | |
| [bool] $Force = $false | |
| ) | |
| if ($Version -eq '') { | |
| Write-Log "Please specify a version. Example: nmp -change 18" -Error | |
| return | |
| } | |
| $versionDir = Join-Path $nodeDir $Version | |
| if ((Test-Path $versionDir) -And -Not $Force) { | |
| Write-Log "Node.js version $Version already installed at $versionDir" | |
| Set-NodePathEntry -NodeDirectory $versionDir | |
| Show-NodeVersion | |
| return | |
| } | |
| # Download and install | |
| try { | |
| $remoteNodeVersions = (Invoke-WebRequest -Uri "https://nodejs.org/dist/" -UseBasicParsing).Links.Href | |
| $versionExists = $remoteNodeVersions | Where-Object { $_ -like "latest-v$Version.x/*" } | |
| if (-Not $versionExists) { | |
| throw "Version $Version is invalid! Run 'nmp -list -remote' to see available versions." | |
| } | |
| # Clean up old installation if force is true | |
| if (Test-Path $versionDir) { | |
| Remove-Item -Path $versionDir -Recurse -Force | Out-Null | |
| } | |
| $tempDir = Join-Path $nodeDir "temp" | |
| if (Test-Path $tempDir) { | |
| Remove-Item -Path $tempDir -Recurse -Force | Out-Null | |
| } | |
| New-Item -ItemType Directory -Path $tempDir | Out-Null | |
| # Get download URL | |
| $downloadUrl = "https://nodejs.org/dist/latest-v$Version.x/" | |
| $response = Invoke-WebRequest -Uri $downloadUrl -UseBasicParsing | |
| $fullPath = ($response.Links.Href | Where-Object { $_ -like "*node-v$Version.*-$Arch.zip" } | Select-Object -First 1) | |
| if ([string]::IsNullOrEmpty($fullPath)) { | |
| throw "No matching Node.js file found for version $Version and architecture $Arch" | |
| } | |
| # Extract just the filename from the full path | |
| $fileName = Split-Path -Leaf $fullPath | |
| $downloadUrl = $downloadUrl + $fileName | |
| Write-Log "Downloading Node.js $Version from $downloadUrl" -ForegroundColor Cyan | |
| Invoke-WebRequest -Uri $downloadUrl -OutFile "$tempDir\$Version.zip" -UseBasicParsing | |
| Write-Log "Downloaded successfully" -ForegroundColor Green | |
| Start-Sleep -Milliseconds 500 | |
| Write-Log "Extracting to $versionDir" -ForegroundColor Cyan | |
| Expand-Archive -Path "$tempDir\$Version.zip" -DestinationPath $versionDir -Force | |
| # Move extracted files up one directory | |
| Get-ChildItem -Path "$versionDir\node-v*" -Directory | ForEach-Object { | |
| Get-ChildItem -Path $_.FullName | Move-Item -Destination $versionDir -Force | |
| } | |
| Remove-Item -Path "$versionDir\node-v*" -Recurse -Force -ErrorAction SilentlyContinue | |
| Remove-Item -Path $tempDir -Recurse -Force | Out-Null | |
| Write-Log "Installation complete" -ForegroundColor Green | |
| # Update PATH | |
| Set-NodePathEntry -NodeDirectory $versionDir | |
| if (Test-CommandExists -Command node -Silent) { | |
| node -v | |
| } else { | |
| Write-Log "Node installed but not yet accessible. Please restart your terminal." -ForegroundColor Yellow | |
| } | |
| } | |
| catch { | |
| Write-Log "Error during installation: $_" -Error | |
| } | |
| } | |
| function nList { | |
| param ([switch] $Remote) | |
| if ($Remote.IsPresent) { | |
| Write-Log "Fetching available Node.js versions..." -ForegroundColor Cyan | |
| (Invoke-WebRequest -Uri "https://nodejs.org/dist/" -UseBasicParsing).Links.Href | | |
| Where-Object { $_ -like "latest-v*" -and $_ -notlike "latest-v0*" } | | |
| ForEach-Object { | |
| $_.Replace('latest-', '').Replace('.x/', '') | |
| } | |
| Write-Log "These are current available Node.js versions (latest)" -ForegroundColor Green | |
| } else { | |
| if (Test-Path $nodeDir) { | |
| Write-Log "Installed Node.js versions:" -ForegroundColor Green | |
| Get-ChildItem -Path $nodeDir -Directory | Where-Object { $_.Name -notlike "temp" } | ForEach-Object { Write-Host $_.Name } | |
| } else { | |
| Write-Log "Node directory does not exist: $nodeDir" -Error | |
| } | |
| } | |
| } | |
| function Show-NodeVersion { | |
| Write-Log "Node Version Manager Powershell (nmp)" -ForegroundColor Cyan | |
| Write-Log $nmpVersion | |
| Write-Log "---------------------------------------" | |
| if (Test-CommandExists -Command node -Silent) { | |
| $nodeV = node -v | |
| Write-Log "Node $nodeV" -ForegroundColor Green | |
| } else { | |
| Write-Log "Node.js is not installed! Run 'nmp -help' for installation options." -ForegroundColor Yellow | |
| } | |
| } | |
| function Show-Help { | |
| Write-Log "Node Version Manager Powershell - Help" -ForegroundColor Cyan | |
| Write-Log "" | |
| Write-Log "-init | Initialize nmp with config file and install default version." | |
| Write-Log "-version | Display current nmp and node versions." | |
| Write-Log "-list | List all installed Node.js versions." | |
| Write-Log " -remote | List all available Node.js versions for installation." | |
| Write-Log "-change <version> | Switch to Node.js version (auto-installs if needed)." | |
| Write-Log " Ex: nmp -change 18 (installs latest v18.x.x)" -ForegroundColor Yellow | |
| Write-Log " -arch | Specify architecture: win-x64 (default) or win-x86." | |
| Write-Log " -force | Force reinstall of existing version." | |
| Write-Log "-setdir <path> | Set Node installation directory." | |
| Write-Log "-setdefault <ver> | Set default Node.js version to auto-load." | |
| Write-Log "-help | Display this help page." | |
| } | |
| # -------- COMMAND ROUTING -------- | |
| if ($Help.IsPresent) { | |
| Show-Help | |
| } elseif ($List.IsPresent) { | |
| nList -Remote:$Remote.IsPresent | |
| } elseif ($Change -ne "") { | |
| nChange -Version $Change -Arch $arch -Force $Force.IsPresent | |
| } elseif ($Version.IsPresent) { | |
| Show-NodeVersion | |
| } elseif ($Init.IsPresent) { | |
| try { | |
| nInit | |
| if (-Not $silentInit) { | |
| Show-NodeVersion | |
| } | |
| } | |
| catch { | |
| Write-Log "Error during initialization: $_" -Error | |
| } | |
| } else { | |
| # Default behavior - show version | |
| Show-NodeVersion | |
| } | |
| } | |
| # ============ INITIALIZATION ============ | |
| # Initialize configuration file | |
| Initialize-Config | |
| # Auto-initialize on script load | |
| nmp -init |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment