ImportDotEnv.psm1
function Get-RelativePath { param ( [string]$Path, [string]$BasePath ) # Cache the directory separator $separator = [System.IO.Path]::DirectorySeparatorChar # Resolve absolute paths $absolutePath = [System.IO.Path]::GetFullPath($Path) $absoluteBasePath = [System.IO.Path]::GetFullPath($BasePath) # Split paths into segments $pathSegments = $absolutePath -split [regex]::Escape($separator) $basePathSegments = $absoluteBasePath -split [regex]::Escape($separator) $commonLength = ( 0..([math]::Min($pathSegments.Length, $basePathSegments.Length) - 1) ).Where({ $pathSegments[$_] -eq $basePathSegments[$_] }).Count # Calculate the relative path using list comprehension if ($commonLength -eq 0) { # No common base path, return the full path return $absolutePath } else { # Use list comprehension to build the relative path $relativePath = @(".") + ($pathSegments[$commonLength..($pathSegments.Length - 1)]) } # Join the segments into a relative path $relativePath = $relativePath -join $separator return $relativePath } # Track previously loaded .env files $script:previousEnvFiles = @() # Track the previous working directory $script:previousWorkingDirectory = (Get-Location).Path function Get-EnvFilesUpstream { param ( [string]$Directory = "." ) # Resolve the full path of the directory try { $resolvedPath = Resolve-Path -Path $Directory -ErrorAction Stop } catch { $resolvedPath = (Get-Location).Path } # Initialize an array to store .env file paths $envFiles = @() # Start from the current directory and move up to the root $currentDir = $resolvedPath while ($currentDir) { $envPath = Join-Path $currentDir ".env" if (Test-Path $envPath -PathType Leaf) { # Add the .env file to the array $envFiles += $envPath } # Move to the parent directory $parentDir = Split-Path $currentDir -Parent if ($parentDir -eq $currentDir) { # Stop if we've reached the root break } $currentDir = $parentDir } return $envFiles } $script:e = [char] 27 $script:itemiser = [char]0x21B3 function Format-EnvFilePath { param ( [string]$Path, [string]$BasePath ) # Resolve the relative path # The RelativeBasePath parameter is available in PowerShell 7.4 and later only $relativePath = Get-RelativePath -Path $Path -BasePath $BasePath # Extract the core path (directory containing the .env file) $corePath = Split-Path $relativePath -Parent # Remove the initial .\ from the relative path $corePath = $corePath -replace '^\.\\', '' # Format the core path in bold $formattedPath = $relativePath -replace ([regex]::Escape($corePath), "$script:e[1m$corePath$script:e[22m") return $formattedPath } function Format-EnvFile { param ( [string]$EnvFile, [string]$BasePath, [string]$Action, # "Load" or "Unload" [string]$ForegroundColor # Color for the action message ) # Initialize a string to store the output $output = "" if (Test-Path $EnvFile -PathType Leaf) { # Format the path $formattedPath = Format-EnvFilePath -Path $EnvFile -BasePath $BasePath Write-Host "$Action .env file ${formattedPath}:" -ForegroundColor $ForegroundColor # Read the file content once $content = Get-Content $EnvFile $lineNumber = 0 # Process the .env file foreach ($line in $content) { $lineNumber++ # Remove comments and trailing whitespace $line = $line -replace '\s*#.*', '' # Match lines that have key=value if ($line -match '^(.*)=(.*)$') { $variableName = $matches[1].Trim() if ($Action -eq "Load") { $variableValue = $matches[2].Trim() $valueToSet = $variableValue $color = "Green" $actionText = "Setting" } else { $valueToSet = $null $color = "Red" $actionText = "Unsetting" } [System.Environment]::SetEnvironmentVariable($variableName, $valueToSet) $fileUrl = "vscode://file/${EnvFile}:$lineNumber" # Add the environment variable action to the output with color and hyperlink $hyperlinkStart = "$script:e]8;;$fileUrl$script:e\" $hyperlinkEnd = "$script:e]8;;$script:e\" $variableString = "$hyperlinkStart$variableName$hyperlinkEnd" Write-Host "$script:itemiser $actionText environment variable: " -NoNewline Write-Host "$variableString" -ForegroundColor "$color" } } } # Return the output as a string return $output } function Format-EnvFiles { param ( [array]$EnvFiles, [string]$BasePath, [string]$Action, # "Load" or "Unload" [string]$Message, # Message to display (e.g., "added" or "removed") [string]$ForegroundColor # Color for the action message ) if ($EnvFiles) { # Initialize a string to store the full output $listOutput = "The following .env files were ${Message}:`n" # Collect formatted paths foreach ($envFile in $EnvFiles) { $formattedPath = Format-EnvFilePath -Path $envFile -BasePath $BasePath $listOutput += "$script:itemiser $formattedPath`n" } # Display the full output at once with colors Write-Host $listOutput -ForegroundColor DarkGray foreach ($envFile in $EnvFiles) { Format-EnvFile -EnvFile $envFile -BasePath $BasePath -Action $Action -ForegroundColor $ForegroundColor } } } function Import-DotEnv { param ( [string]$Path = "." ) # Resolve the full path of the directory try { $resolvedPath = Resolve-Path -Path $Path -ErrorAction Stop } catch { $resolvedPath = (Get-Location).Path } # Get the current list of .env files $currentEnvFiles = Get-EnvFilesUpstream -Directory $resolvedPath $previousEnvFilesSet = [System.Collections.Generic.HashSet[string]]::new() foreach ($file in $script:previousEnvFiles) { [void]$previousEnvFilesSet.Add($file) } $currentEnvFilesSet = [System.Collections.Generic.HashSet[string]]::new() foreach ($file in $currentEnvFiles) { [void]$currentEnvFilesSet.Add($file) } # Compare with the previous list to detect removed .env files $removedEnvFiles = $script:previousEnvFiles | Where-Object { -not $currentEnvFilesSet.Contains($_) } # Compare with the previous list to detect added .env files $addedEnvFiles = $currentEnvFiles | Where-Object { -not $previousEnvFilesSet.Contains($_) } # Process removed .env files (relative to current path) Format-EnvFiles -EnvFiles $removedEnvFiles -BasePath $resolvedPath -Action "Unload" -Message "removed" -ForegroundColor Yellow # Process added .env files (relative to previous path) Format-EnvFiles -EnvFiles $addedEnvFiles -BasePath $script:previousWorkingDirectory -Action "Load" -Message "added" -ForegroundColor Cyan # Update the previous list with the current list $script:previousEnvFiles = $currentEnvFiles # Update the previous working directory $script:previousWorkingDirectory = $resolvedPath } function Set-Location { param ( [string]$Path ) Microsoft.PowerShell.Management\Set-Location $Path Import-DotEnv } Export-ModuleMember -Function Get-EnvFilesUpstream, Import-DotEnv, Set-Location |