#!/usr/bin/env pwsh
# ======================================================================================
# common.ps1 — Shared infrastructure for entropy-auth CI/CD scripts
# ======================================================================================
#
# Provides logging, operation timing, and tool verification functions used across
# build.ps1, test.ps1, and promote.ps1.
#
# Usage: Dot-source this file after any param() block but before function calls:
#
# . "$PSScriptRoot/common.ps1"
#
# Prerequisites (must be set by the calling script BEFORE first function call):
#
# $Script:LOG_LEVEL — Logging verbosity: DEBUG, INFO, WARNING, ERROR
# $Script:LOG_FILE_NAME — Log file basename (e.g. "entropy_auth_build.log")
#
# ======================================================================================
# Runtime performance tracking arrays — initialised once per script invocation.
# Because this file is dot-sourced, $Script: refers to the caller's scope.
# Set-StrictMode -Version Latest makes reading an unset variable throw, so probe
# for existence with Get-Variable rather than dereferencing the variable.
if (-not (Get-Variable -Name 'TRACKED_OP_NAMES' -Scope 'Script' -ErrorAction SilentlyContinue))
{
$Script:TRACKED_OP_NAMES = @()
$Script:TRACKED_OP_STARTS = @()
$Script:TRACKED_OP_ELAPSED = @()
}
# ======================
# Logging
# ======================
function Get-LogPriority
{
param (
[string]$level
)
switch ($level)
{
"ERROR"
{ 0
}
"WARNING"
{ 1
}
"INFO"
{ 2
}
"DEBUG"
{ 3
}
default
{ -1
}
}
}
# Structured logging with timestamps and verbosity filtering
function Write-Log
{
param (
[string]$LogLevel,
[string]$LogMessage,
[string]$OperationName = ""
)
$timestamp = Get-Date -Format "yyyy-MM-dd HH:mm:ss"
# Construct the formatted log line
$outputLine = "[$LogLevel] [$timestamp] $LogMessage"
# Append elapsed time when an operation name is supplied on INFO messages
if ($LogLevel -eq "INFO" -and $OperationName -ne "")
{
$elapsed = Get-OperationTime -Operation $OperationName -Type "duration"
if ($null -ne $elapsed -and $elapsed -ne "")
{
$readableDuration = Format-Duration -Seconds $elapsed
$outputLine = "$outputLine (Duration: $readableDuration)"
}
}
# Resolve the user home directory (cross-platform)
$userHome = if ($IsWindows)
{
$env:USERPROFILE
} else
{
$env:HOME
}
# Platform-appropriate log directory
$logDirectory = if ($IsWindows)
{
Join-Path -Path $userHome -ChildPath "AppData\Local\Logs"
} elseif ($IsMacOS)
{
Join-Path -Path $userHome -ChildPath "Library/Logs"
} else
{
Join-Path -Path $userHome -ChildPath ".local/share/logs"
}
# Use the script-specific log file name, falling back to a generic default
$logFileName = if (-not [string]::IsNullOrEmpty($Script:LOG_FILE_NAME))
{
$Script:LOG_FILE_NAME
} else
{
"entropy_auth.log"
}
$logFilePath = Join-Path -Path $logDirectory -ChildPath $logFileName
# Ensure the log directory exists
if (-not (Test-Path -Path $logDirectory))
{
New-Item -Path $logDirectory -ItemType Directory -Force | Out-Null
}
# Compare message priority against the configured threshold
$msgPriority = Get-LogPriority -Level $LogLevel
$thresholdPriority = Get-LogPriority -Level $Script:LOG_LEVEL
# Emit only when the message severity meets or exceeds the threshold
if ($msgPriority -ge 0 -and $thresholdPriority -ge 0 -and $msgPriority -le $thresholdPriority)
{
Write-Host $outputLine
Add-Content -Path $logFilePath -Value $outputLine
}
}
# ======================
# Operation Timing
# ======================
# Persist operation timing entries
function Set-OperationTime
{
param (
[string]$Operation,
$Value,
[string]$Type # "start" or "duration"
)
$index = -1
# Locate an existing entry or allocate a new one
for ($i = 0; $i -lt $Script:TRACKED_OP_NAMES.Count; $i++)
{
if ($Script:TRACKED_OP_NAMES[$i] -eq $Operation)
{
$index = $i
break
}
}
if ($index -eq -1)
{
$index = $Script:TRACKED_OP_NAMES.Count
$Script:TRACKED_OP_NAMES += $Operation
$Script:TRACKED_OP_STARTS += ""
$Script:TRACKED_OP_ELAPSED += ""
}
if ($Type -eq "start")
{
$Script:TRACKED_OP_STARTS[$index] = $Value
} else
{
$Script:TRACKED_OP_ELAPSED[$index] = $Value
}
}
# Retrieve operation timing entries
function Get-OperationTime
{
param (
[string]$Operation,
[string]$Type # "start" or "duration"
)
for ($i = 0; $i -lt $Script:TRACKED_OP_NAMES.Count; $i++)
{
if ($Script:TRACKED_OP_NAMES[$i] -eq $Operation)
{
if ($Type -eq "start")
{
return $Script:TRACKED_OP_STARTS[$i]
} else
{
return $Script:TRACKED_OP_ELAPSED[$i]
}
}
}
return ""
}
# Begin timing a named operation
function Start-OperationTiming
{
param (
[string]$Operation
)
$epochSeconds = [int](Get-Date -UFormat %s)
Set-OperationTime -Operation $Operation -Value $epochSeconds -Type "start"
}
# End timing a named operation and compute elapsed seconds
function Stop-OperationTiming
{
param (
[string]$Operation
)
$epochSeconds = [int](Get-Date -UFormat %s)
$started = Get-OperationTime -Operation $Operation -Type "start"
if ($started -ne "")
{
$delta = $epochSeconds - $started
Set-OperationTime -Operation $Operation -Value $delta -Type "duration"
}
}
# Format seconds into a human-readable duration string
function Format-Duration
{
param (
[int]$Seconds
)
$hours = [math]::Floor($Seconds / 3600)
$minutes = [math]::Floor(($Seconds % 3600) / 60)
$remainingSeconds = $Seconds % 60
if ($hours -gt 0)
{
return "{0}h {1}m {2}s" -f $hours, $minutes, $remainingSeconds
} elseif ($minutes -gt 0)
{
return "{0}m {1}s" -f $minutes, $remainingSeconds
} else
{
return "{0}s" -f $remainingSeconds
}
}
# Print a summary table of all tracked operation timings
function Show-TimingSummary
{
Write-Host "`nOperation Timing Summary:"
Write-Host "------------------------"
for ($i = 0; $i -lt $Script:TRACKED_OP_NAMES.Count; $i++)
{
$opName = $Script:TRACKED_OP_NAMES[$i]
$opDuration = $Script:TRACKED_OP_ELAPSED[$i]
if (-not [string]::IsNullOrEmpty($opDuration))
{
$formatted = Format-Duration -Seconds $opDuration
"{0,-25}: {1}" -f $opName, $formatted | Write-Host
}
}
}
# ======================
# Tool Verification
# ======================
# Verify that a tool is installed and functional
function Test-ToolInstallation
{
param (
[string]$ToolName,
[string]$VersionFlag = "--version"
)
Write-Log -LogLevel "DEBUG" -LogMessage "Checking $ToolName installation..."
if (-not (Get-Command $ToolName -ErrorAction SilentlyContinue))
{
Write-Log -LogLevel "ERROR" -LogMessage "$ToolName is not installed or not in PATH"
return $false
}
try
{
$null = & $ToolName $VersionFlag 2>$null
if ($LASTEXITCODE -ne 0)
{
throw "$ToolName version check failed"
}
} catch
{
Write-Log -LogLevel "ERROR" -LogMessage "$ToolName is installed but not functioning properly"
return $false
}
Write-Log -LogLevel "INFO" -LogMessage "$ToolName installation verified"
return $true
}