PwSh.Fw.NodeJs.psm1
|
## @file PwShNode.psm1 ## @brief Cross-platform PowerShell module for managing Node.js installations ## @requires PowerShell 7.0+ $Script:RELEASEURL = "https://api.github.com/repos/nodejs/node/releases" if ($IsWindows -or $PSVersionTable.Platform -eq 'Win32NT') { $Script:configDir = Join-Path $env:LOCALAPPDATA 'PwSh.Fw' } else { # Linux/macOS: use XDG_CONFIG_HOME or ~/.config $configHome = $env:XDG_CONFIG_HOME if ([string]::IsNullOrEmpty($configHome)) { $configHome = Join-Path $HOME '.config' } $Script:configDir = Join-Path $configHome 'pwsh.fw' } if (!(Test-Path $Script:configDir)) { New-Item -ItemType Directory -Path $Script:configDir -Force | Out-Null } $Script:ConfigFile = Join-Path $Script:configDir 'pwsh.fw.nodejs.json' <# .SYNOPSIS Retrieves Node.js version information. .DESCRIPTION Queries the GitHub API to retrieve the latest available Node.js version, either the Current (latest release) or LTS (Long Term Support) version. .PARAMETER Channel The release channel to use: 'Current' for the latest version or 'LTS' for long-term support. Default: 'LTS' .OUTPUTS PSCustomObject containing Version, Name, Channel, PublishedAt and Assets. .EXAMPLE Get-PwShNodeVersion -Channel LTS Retrieves information about the latest LTS version. .EXAMPLE Get-PwShNodeVersion -Channel Current Retrieves information about the latest Current version. .NOTES This function queries the GitHub API and may be subject to rate limiting. #> function Get-PwShNodeVersion { [CmdletBinding()] [OutputType([PSCustomObject])] Param ( [Parameter(Mandatory = $false)] [ValidateSet('Current', 'LTS')] [string]$Channel = 'LTS' ) Begin { Write-EnterFunction } Process { try { switch ($Channel) { 'Current' { Write-Verbose "Fetching latest Current release from GitHub API" $release = Invoke-RestMethod -Uri "$Script:RELEASEURL/latest" } 'LTS' { Write-Verbose "Fetching latest LTS release from GitHub API" $releases = Invoke-RestMethod -Uri $Script:RELEASEURL $release = ($releases | Where-Object { $_.Name -like "*(LTS)*" })[0] } } $versionInfo = [PSCustomObject]@{ Version = $release.tag_name Name = $release.name Channel = $Channel PublishedAt = $release.published_at Assets = $release.assets.name } return $versionInfo } catch { Write-Error "Failed to retrieve Node.js version: $_" throw } } End { Write-LeaveFunction } } <# .SYNOPSIS Sets the default Node.js version in configuration. .DESCRIPTION Saves the specified Node.js version as the default version in the configuration file. This does not install the version, only marks it as the default. .PARAMETER Version The Node.js version to set as default (e.g., 'v20.11.0'). .EXAMPLE Set-PwShNodeVersion -Version 'v20.11.0' Sets v20.11.0 as the default Node.js version. .EXAMPLE 'v18.19.0' | Set-PwShNodeVersion Sets v18.19.0 as the default using pipeline input. .NOTES This function creates the configuration directory if it doesn't exist. #> function Set-PwShNodeVersion { [CmdletBinding()] [OutputType([void])] Param ( [Parameter(Mandatory = $true, ValueFromPipeline = $true)] [string]$Version ) Begin { Write-EnterFunction } Process { try { $config = @{ DefaultVersion = $Version LastUpdated = (Get-Date).ToString('o') } if (Test-Path $Script:ConfigFile) { $existingConfig = Get-Content $Script:ConfigFile | ConvertFrom-Json -AsHashtable if ($existingConfig.InstallLocation) { $config.InstallLocation = $existingConfig.InstallLocation } } $config | ConvertTo-Json | Set-Content -Path $Script:ConfigFile Write-Verbose "Node.js version set to: $Version" } catch { Write-Error "Failed to set Node.js version: $_" throw } } End { Write-LeaveFunction } } <# .SYNOPSIS Gets the Node.js installation directory path. .DESCRIPTION Retrieves the configured installation directory for Node.js versions. Returns the platform-specific default location if not explicitly configured. .OUTPUTS String containing the installation directory path. .EXAMPLE Get-PwShNodeInstallLocation Returns the current installation directory path. .EXAMPLE $installPath = Get-PwShNodeInstallLocation Stores the installation path in a variable. .NOTES Default locations: - Windows: %LOCALAPPDATA%\PwShNode - Linux/macOS: $XDG_DATA_HOME/pwshnode or ~/.local/share/pwshnode #> function Get-PwShNodeInstallLocation { [CmdletBinding()] [OutputType([string])] Param () Begin { Write-EnterFunction } Process { try { if (Test-Path $Script:ConfigFile) { $config = Get-Content $Script:ConfigFile | ConvertFrom-Json if ($config.InstallLocation) { return $config.InstallLocation } } # Default location based on platform $defaultLocation = Get-PwShNodeDefaultInstallLocation Write-Verbose "Using default install location: $defaultLocation" return $defaultLocation } catch { Write-Error "Failed to get install location: $_" throw } } End { Write-LeaveFunction } } <# .SYNOPSIS Sets the Node.js installation directory path. .DESCRIPTION Configures the directory where Node.js versions will be installed. Creates the directory if it doesn't exist. .PARAMETER Path The directory path where Node.js versions should be installed. .EXAMPLE Set-PwShNodeInstallLocation -Path 'C:\NodeJS' Sets the installation directory to C:\NodeJS on Windows. .EXAMPLE Set-PwShNodeInstallLocation -Path '/opt/nodejs' Sets the installation directory to /opt/nodejs on Linux/macOS. .EXAMPLE 'C:\Dev\NodeJS' | Set-PwShNodeInstallLocation Sets the installation directory using pipeline input. .NOTES The directory will be created if it doesn't exist. #> function Set-PwShNodeInstallLocation { [CmdletBinding()] [OutputType([void])] Param ( [Parameter(Mandatory = $true, ValueFromPipeline = $true)] [string]$Path ) Begin { Write-EnterFunction } Process { try { if (!(Test-Path $Path)) { New-Item -ItemType Directory -Path $Path -Force | Out-Null Write-Verbose "Created install directory: $Path" } $config = @{ InstallLocation = $Path LastUpdated = (Get-Date).ToString('o') } if (Test-Path $Script:ConfigFile) { $existingConfig = Get-Content $Script:ConfigFile | ConvertFrom-Json -AsHashtable if ($existingConfig.DefaultVersion) { $config.DefaultVersion = $existingConfig.DefaultVersion } } $config | ConvertTo-Json | Set-Content -Path $Script:ConfigFile Write-Verbose "Install location set to: $Path" } catch { Write-Error "Failed to set install location: $_" throw } } End { Write-LeaveFunction } } <# .SYNOPSIS Installs a specific Node.js version. .DESCRIPTION Downloads and installs a Node.js version for the current or specified platform and architecture. Supports automatic platform/architecture detection, caching, and setting as default version. .PARAMETER Version The Node.js version to install (e.g., 'v20.11.0'). If not specified, installs the latest version from the specified Channel. .PARAMETER Channel The release channel: 'Current' for latest or 'LTS' for long-term support. Only used when Version is not specified. Default: 'LTS' .PARAMETER Architecture The target architecture: 'x64', 'x86', 'arm64', or 'armv7l'. If not specified, auto-detects the current system architecture. .PARAMETER Platform The target platform: 'win', 'linux', or 'darwin'. If not specified, auto-detects the current platform. .PARAMETER CacheDir Directory for caching downloaded archives. If not specified, uses a 'cache' subdirectory in the installation location. .PARAMETER Force Forces reinstallation even if the version is already installed. .OUTPUTS PSCustomObject containing Version, Path, Platform, Architecture, and Installed status. .EXAMPLE Install-PwShNodeVersion Installs the latest LTS version for the current platform. .EXAMPLE Install-PwShNodeVersion -Version 'v20.11.0' Installs a specific Node.js version. .EXAMPLE Install-PwShNodeVersion -Channel Current -Force Installs the latest Current version, forcing reinstallation. .EXAMPLE Install-PwShNodeVersion -Platform 'linux' -Architecture 'arm64' Installs for a specific platform and architecture. .NOTES Downloaded files are cached to avoid re-downloading. On Unix systems, executable permissions are automatically set. #> function Install-PwShNodeVersion { [CmdletBinding()] [OutputType([PSCustomObject])] Param ( [Parameter(Mandatory = $false, ValueFromPipeline = $true)] [string]$Version, [Parameter(Mandatory = $false)] [ValidateSet('Current', 'LTS')] [string]$Channel = 'LTS', [Parameter(Mandatory = $false)] [string]$Architecture = (Get-PwShNodeArchitecture), [Parameter(Mandatory = $false)] [string]$Platform = (Get-PwShNodePlatform), [Parameter(Mandatory = $false)] [string]$CacheDir, [Parameter(Mandatory = $false)] [switch]$Force ) Begin { Write-EnterFunction } Process { try { # Get version if not specified if ([string]::IsNullOrEmpty($Version)) { $versionInfo = Get-PwShNodeVersion -Channel $Channel $Version = $versionInfo.Version Write-Verbose "Retrieved version: $Version" } # Determine install location $installLocation = Get-PwShNodeInstallLocation $versionPath = Join-Path $installLocation $Version if ((Test-Path $versionPath) -and !$Force) { Write-Warning "Version $Version already installed at $versionPath. Use -Force to reinstall." return [PSCustomObject]@{ Version = $Version Path = $versionPath AlreadyInstalled = $true } } # Determine filename and extension $extension = if ($Platform -eq 'linux' -or $Platform -eq 'darwin') { 'tar.gz' } else { 'zip' } $filename = "node-$Version-$Platform-$Architecture.$extension" $url = "https://nodejs.org/dist/$Version/$filename" # Setup cache directory if ([string]::IsNullOrEmpty($CacheDir)) { $CacheDir = Join-Path $installLocation 'cache' } if (!(Test-Path $CacheDir)) { New-Item -ItemType Directory -Path $CacheDir -Force | Out-Null } $cacheFile = Join-Path $CacheDir $filename # Download if not cached if (!(Test-Path $cacheFile)) { Write-Verbose "Downloading $filename from $url" Write-Info "Downloading Node.js $Version..." Invoke-WebRequest -Uri $url -OutFile $cacheFile -UseBasicParsing } else { Write-Verbose "Using cached file: $cacheFile" } # Extract based on platform Write-Verbose "Extracting to $installLocation" $extractedFolder = Join-Path $installLocation "node-$Version-$Platform-$Architecture" if ($extension -eq 'zip') { # Windows Expand-Archive -Path $cacheFile -DestinationPath $installLocation -Force } else { # Linux/macOS - using tar $tarArgs = @('-xzf', $cacheFile, '-C', $installLocation) $tarProcess = Start-Process -FilePath 'tar' -ArgumentList $tarArgs -NoNewWindow -Wait -PassThru if ($tarProcess.ExitCode -ne 0) { throw "tar extraction failed with exit code $($tarProcess.ExitCode)" } } # Rename extracted folder to version if (Test-Path $extractedFolder) { if (Test-Path $versionPath) { Remove-Item $versionPath -Recurse -Force } Move-Item -Path $extractedFolder -Destination $versionPath } else { throw "Extraction failed: expected folder not found at $extractedFolder" } # Set executable permissions on Unix-like systems if ($Platform -eq 'linux' -or $Platform -eq 'darwin') { $binPath = Join-Path $versionPath 'bin' if (Test-Path $binPath) { Get-ChildItem -Path $binPath -File | ForEach-Object { chmod +x $_.FullName } } } # Set as default version Set-PwShNodeVersion -Version $Version Write-Info "Node.js $Version installed successfully at $versionPath" -ForegroundColor Green return [PSCustomObject]@{ Version = $Version Path = $versionPath Platform = $Platform Architecture = $Architecture Installed = $true } } catch { Write-Error "Failed to install Node.js version ${Version}: $_" throw } } End { Write-LeaveFunction } } <# .SYNOPSIS Removes an installed Node.js version. .DESCRIPTION Uninstalls a specific Node.js version by removing its installation directory. Supports -WhatIf and -Confirm for safe operations. .PARAMETER Version The Node.js version to remove (e.g., 'v20.11.0'). .PARAMETER Force Bypasses confirmation prompts (use with -Confirm:$false). .EXAMPLE Remove-PwShNodeVersion -Version 'v18.19.0' Removes the specified version with confirmation prompt. .EXAMPLE Remove-PwShNodeVersion -Version 'v18.19.0' -Confirm:$false Removes the version without confirmation. .EXAMPLE 'v16.20.0' | Remove-PwShNodeVersion Removes the version using pipeline input. .EXAMPLE Remove-PwShNodeVersion -Version 'v20.11.0' -WhatIf Shows what would be removed without actually removing it. .NOTES This operation cannot be undone. Use -WhatIf to preview changes. #> function Remove-PwShNodeVersion { [CmdletBinding(SupportsShouldProcess = $true, ConfirmImpact = 'High')] [OutputType([void])] Param ( [Parameter(Mandatory = $true, ValueFromPipeline = $true)] [string]$Version, [Parameter(Mandatory = $false)] [switch]$Force ) Begin { Write-EnterFunction } Process { try { $installLocation = Get-PwShNodeInstallLocation $versionPath = Join-Path $installLocation $Version if (!(Test-Path $versionPath)) { Write-Warning "Version $Version is not installed at $versionPath" return } if ($PSCmdlet.ShouldProcess($versionPath, "Remove Node.js version")) { Remove-Item -Path $versionPath -Recurse -Force Write-Verbose "Removed Node.js version $Version from $versionPath" Write-Info "Node.js $Version removed successfully" -ForegroundColor Green } } catch { Write-Error "Failed to remove Node.js version ${Version}: $_" throw } } End { Write-LeaveFunction } } # Private helper functions <# .SYNOPSIS Detects the current operating system platform. .DESCRIPTION Returns the platform identifier used by Node.js: 'win', 'linux', or 'darwin'. .OUTPUTS String containing the platform identifier. .NOTES This is an internal helper function. #> function Get-PwShNodePlatform { [CmdletBinding()] [OutputType([string])] Param() if ($IsWindows -or $PSVersionTable.Platform -eq 'Win32NT') { return 'win' } elseif ($IsLinux) { return 'linux' } elseif ($IsMacOS) { return 'darwin' } else { throw "Unsupported platform" } } <# .SYNOPSIS Detects the current system architecture. .DESCRIPTION Returns the architecture identifier used by Node.js: 'x64', 'x86', 'arm64', or 'armv7l'. .OUTPUTS String containing the architecture identifier. .NOTES This is an internal helper function. #> function Get-PwShNodeArchitecture { [CmdletBinding()] [OutputType([string])] Param() $arch = [System.Runtime.InteropServices.RuntimeInformation]::OSArchitecture switch ($arch) { 'X64' { return 'x64' } 'X86' { return 'x86' } 'Arm64' { return 'arm64' } 'Arm' { return 'armv7l' } default { throw "Unsupported architecture: $arch" } } } <# .SYNOPSIS Gets the platform-specific default installation directory. .DESCRIPTION Returns the default installation directory path, respecting platform conventions: - Windows: %LOCALAPPDATA%\PwShNode - Linux/macOS: $XDG_DATA_HOME/pwshnode or ~/.local/share/pwshnode .OUTPUTS String containing the default installation directory path. .NOTES This is an internal helper function. #> function Get-PwShNodeDefaultInstallLocation { [CmdletBinding()] [OutputType([string])] Param() if ($IsWindows -or $PSVersionTable.Platform -eq 'Win32NT') { return Join-Path $env:LOCALAPPDATA 'PwShNode' } else { # Linux/macOS: use XDG_DATA_HOME or ~/.local/share $dataHome = $env:XDG_DATA_HOME if ([string]::IsNullOrEmpty($dataHome)) { $dataHome = Join-Path $HOME '.local' 'share' } return Join-Path $dataHome 'pwshnode' } } <# .SYNOPSIS Clears the Node.js download cache. .DESCRIPTION Removes all cached Node.js installation archives from the cache directory. This frees up disk space but will require re-downloading files for future installations. .PARAMETER Force Bypasses confirmation prompts (use with -Confirm:$false). .EXAMPLE Clear-PwShNodeCache Clears the cache with confirmation prompt. .EXAMPLE Clear-PwShNodeCache -Confirm:$false Clears the cache without confirmation. .EXAMPLE Clear-PwShNodeCache -WhatIf Shows what would be removed without actually removing it. .NOTES This operation cannot be undone. Cached files will need to be re-downloaded. #> function Clear-PwShNodeCache { [CmdletBinding(SupportsShouldProcess = $true, ConfirmImpact = 'Medium')] [OutputType([void])] Param ( [Parameter(Mandatory = $false)] [switch]$Force ) Begin { Write-EnterFunction } Process { try { $installLocation = Get-PwShNodeInstallLocation $cacheDir = Join-Path $installLocation 'cache' if (!(Test-Path $cacheDir)) { Write-Warning "Cache directory does not exist at $cacheDir" return } $cacheFiles = Get-ChildItem -Path $cacheDir -File $cacheSize = ($cacheFiles | Measure-Object -Property Length -Sum).Sum $cacheSizeMB = [math]::Round($cacheSize / 1MB, 2) $fileCount = $cacheFiles.Count if ($fileCount -eq 0) { Write-Info "Cache is already empty" -ForegroundColor Green return } $message = "Remove $fileCount cached file(s) ($cacheSizeMB MB) from $cacheDir" if ($PSCmdlet.ShouldProcess($cacheDir, $message)) { Remove-Item -Path $cacheDir -Recurse -Force New-Item -ItemType Directory -Path $cacheDir -Force | Out-Null Write-Verbose "Cleared cache directory: $cacheDir" Write-Info "Cache cleared successfully ($cacheSizeMB MB freed)" -ForegroundColor Green } } catch { Write-Error "Failed to clear cache: $_" throw } } End { Write-LeaveFunction } } |