Command Palette

Search for a command to run...

0GitHub stars
Components

Sync Env

PowerShell utility to synchronize and reconcile local environment files against template configurations.

Loading…

Features

  • Scans and detects missing environment keys from .env.example without 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-env

Usage

Reconcile local .env against .env.example:

.\sync-env.ps1

Run in interactive mode to specify missing variables on the fly:

.\sync-env.ps1 -Interactive

Preview differences without modifying .env:

.\sync-env.ps1 -DryRun

Target custom environment files:

.\sync-env.ps1 -Target ".env.production" -Example ".env.production.example"

Parameters

ParameterTypeDefaultDescription
-Targetstring.envDestination environment file path
-Examplestring.env.exampleTemplate environment file path
-InteractiveswitchfalsePrompt for missing values interactively
-DryRunswitchfalseDisplay 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)
 

Was this helpful?

Command Palette

Search for a command to run...