Sync Env
PowerShell utility to synchronize and reconcile local environment files against template configurations.
Features
- Scans and detects missing environment keys from
.env.examplewithout overwriting existing secrets. - Supports interactive prompting to supply missing values during runtime.
- Dry-run mode to inspect proposed environment differences safely.
- Cross-platform PowerShell Core (
pwsh) and Windows PowerShell 5.1+ compatibility. - Clean formatted terminal feedback tags (
[OK],[MISSING],[SUCCESS]).
Installation
Install-Module cnpx -Scope CurrentUser -Force; Import-Module cnpx; cnpx add @chadnpc/sync-envUsage
Reconcile local .env against .env.example:
.\sync-env.ps1Run in interactive mode to specify missing variables on the fly:
.\sync-env.ps1 -InteractivePreview differences without modifying .env:
.\sync-env.ps1 -DryRunTarget custom environment files:
.\sync-env.ps1 -Target ".env.production" -Example ".env.production.example"Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
-Target | string | .env | Destination environment file path |
-Example | string | .env.example | Template environment file path |
-Interactive | switch | false | Prompt for missing values interactively |
-DryRun | switch | false | Display differences without writing to disk |
Source code
<#
.SYNOPSIS
Synchronizes environment variables between a template file (.env.example) and a local target (.env).
.DESCRIPTION
Compares keys defined in an example environment file against a target environment file.
Identifies missing keys, prevents accidental secret overwrites, and optionally appends or
prompts for missing variables.
.PARAMETER Target
Path to the destination environment file. Defaults to '.env'.
.PARAMETER Example
Path to the template environment file. Defaults to '.env.example'.
.PARAMETER Interactive
When specified, prompts for missing values interactively.
.PARAMETER DryRun
Displays the differences and proposed actions without modifying any files.
.EXAMPLE
.\sync-env.ps1
Compares .env against .env.example and appends missing keys with blank values.
.EXAMPLE
.\sync-env.ps1 -Interactive
Compares and prompts for each missing key interactively before updating .env.
#>
[CmdletBinding()]
param(
[Parameter(Position = 0)]
[string]$Target = ".env",
[Parameter(Position = 1)]
[string]$Example = ".env.example",
[switch]$Interactive,
[switch]$DryRun
)
Set-StrictMode -Version Latest
$ErrorActionPreference = "Stop"
function Parse-EnvFile {
param([string]$FilePath)
$envMap = [ordered]@{}
if (-not (Test-Path -Path $FilePath)) {
return $envMap
}
$lines = Get-Content -Path $FilePath
foreach ($line in $lines) {
$trimmed = $line.Trim()
if ([string]::IsNullOrWhiteSpace($trimmed) -or $trimmed.StartsWith("#")) {
continue
}
$separatorIndex = $trimmed.IndexOf('=')
if ($separatorIndex -gt 0) {
$key = $trimmed.Substring(0, $separatorIndex).Trim()
$value = $trimmed.Substring($separatorIndex + 1).Trim()
$envMap[$key] = $value
}
}
return $envMap
}
function Write-Status {
param(
[string]$Tag,
[string]$Message,
[ConsoleColor]$Color = [ConsoleColor]::White
)
Write-Host "[$Tag] " -NoNewline -ForegroundColor $Color
Write-Host $Message
}
# Validate example file existence
if (-not (Test-Path -Path $Example)) {
Write-Status "ERROR" "Template file '$Example' was not found." ([ConsoleColor]::Red)
exit 1
}
Write-Status "INFO" "Analyzing environment configuration..." ([ConsoleColor]::Cyan)
Write-Status "INFO" "Template: $Example" ([ConsoleColor]::DarkGray)
Write-Status "INFO" "Target: $Target" ([ConsoleColor]::DarkGray)
$exampleKeys = Parse-EnvFile -FilePath $Example
$targetKeys = Parse-EnvFile -FilePath $Target
$targetExists = Test-Path -Path $Target
if (-not $targetExists) {
Write-Status "WARN" "Target file '$Target' does not exist. It will be created." ([ConsoleColor]::Yellow)
}
$missingKeys = [System.Collections.Generic.List[string]]::new()
$matchedKeys = [System.Collections.Generic.List[string]]::new()
foreach ($key in $exampleKeys.Keys) {
if ($targetKeys.Contains($key)) {
$matchedKeys.Add($key)
} else {
$missingKeys.Add($key)
}
}
# Display status of matched keys
foreach ($key in $matchedKeys) {
Write-Status "OK" "$key is configured" ([ConsoleColor]::Green)
}
if ($missingKeys.Count -eq 0) {
Write-Status "SUCCESS" "All $($exampleKeys.Count) environment variable(s) are in sync." ([ConsoleColor]::Green)
exit 0
}
Write-Status "WARN" "Found $($missingKeys.Count) missing variable(s):" ([ConsoleColor]::Yellow)
$newEntries = [System.Collections.Generic.List[string]]::new()
foreach ($key in $missingKeys) {
$defaultValue = $exampleKeys[$key]
$valueToSet = $defaultValue
if ($Interactive -and -not $DryRun) {
$promptMsg = "Enter value for $key [default: '$defaultValue']"
$inputVal = Read-Host -Prompt $promptMsg
if (-not [string]::IsNullOrWhiteSpace($inputVal)) {
$valueToSet = $inputVal
}
}
Write-Status "MISSING" "$key -> adding with default: '$valueToSet'" ([ConsoleColor]::Yellow)
$newEntries.Add("$key=$valueToSet")
}
if ($DryRun) {
Write-Status "DRYRUN" "Dry run mode active. No changes written to '$Target'." ([ConsoleColor]::Magenta)
exit 0
}
# Write missing entries to target file
$appendContent = [System.Text.StringBuilder]::new()
if ($targetExists) {
$existingContent = Get-Content -Path $Target -Raw
if ($existingContent -and -not $existingContent.EndsWith([Environment]::NewLine)) {
$null = $appendContent.AppendLine()
}
}
$null = $appendContent.AppendLine("# Appended by sync-env.ps1 on $(Get-Date -Format 'yyyy-MM-dd HH:mm:ss')")
foreach ($entry in $newEntries) {
$null = $appendContent.AppendLine($entry)
}
[System.IO.File]::AppendAllText((Resolve-Path -Path $Target -ErrorAction SilentlyContinue ?? (Join-Path (Get-Location) $Target)), $appendContent.ToString())
Write-Status "SUCCESS" "Successfully synced $($missingKeys.Count) variable(s) to '$Target'." ([ConsoleColor]::Green)