Public/Timing/Export-PhaseTimingTreeIfRequested.ps1

<#
.NOTES
    Self-guarding opt-in wrapper over Export-PhaseTimingTree. Every child
    emitter (provision.ps1, create-users.ps1, remove-users.ps1,
    register-runners.ps1) used to hand-write the same guard at each export
    site - read the TIMING_TREE_OUTPUT_PATH env var, and only when it is set
    call Export-PhaseTimingTree -Path with it. That idiom hard-coded the
    contract variable name at every call site, so a typo in one consumer
    silently disabled its export with no error. This verb centralises both the
    guard and the env-var name so the opt-in contract is single-sourced (the
    name lives in exactly one place) and each call site becomes one
    intention-revealing call.

    The core Export-TimingSpanTree stays mandatory-`-Tree` and its shim
    Export-PhaseTimingTree stays mandatory-`-Path` on purpose: the env-var read
    lives only here, in the opt-in-aware layer, preserving the
    context-explicit core / opt-in-aware shim split the module was built on
    (see Export-PhaseTimingTree and Write-PhaseTimingReport).
#>


function Export-PhaseTimingTreeIfRequested {
<#
.SYNOPSIS
    Serialise the default 2-level timing context to the cross-process JSON
    export path named by an environment variable, only when it is set.

.DESCRIPTION
    The opt-in front door to Export-PhaseTimingTree. Reads the output-path
    environment variable (default TIMING_TREE_OUTPUT_PATH) and, when it holds a
    non-empty value, delegates to Export-PhaseTimingTree to write the default
    context there; when it is unset or empty, it does nothing. The
    "context was never initialised" case is a further no-op, inherited from
    Export-PhaseTimingTree's own null-guard (parity with Write-PhaseTimingReport).

    Designed to be called unconditionally from a child emitter's outer
    try/finally (and any early-exit path that bypasses it): a parent
    orchestrator sets the env var before shelling out and imports the artifact
    afterwards to graft the child's timings under the part that ran it; an
    operator who never sets it sees unchanged behaviour (no file written).

.PARAMETER EnvVariableName
    Name of the environment variable that carries the destination path.
    Defaults to the neutral cross-process contract name TIMING_TREE_OUTPUT_PATH;
    overridable so a caller with its own opt-in convention can reuse the guard.

.EXAMPLE
    try {
        # ... timed stages ...
    }
    finally {
        Export-PhaseTimingTreeIfRequested
    }
#>


    [CmdletBinding()]
    param(
        [Parameter()]
        [ValidateNotNullOrEmpty()]
        [string] $EnvVariableName = 'TIMING_TREE_OUTPUT_PATH'
    )

    # Read the env var by name (not the $env:LITERAL idiom) so -EnvVariableName
    # is honoured. An unset variable returns $null and an explicitly empty one
    # returns '' - both are falsy, so the opt-in stays off unless a real path
    # is present, matching the hand-written `if ($env:TIMING_TREE_OUTPUT_PATH)`
    # guard this replaces.
    $outputPath = [Environment]::GetEnvironmentVariable($EnvVariableName)
    if (-not $outputPath) {
        return
    }

    # Delegate the write (and the "never initialised" null-guard) to the
    # context-reading shim so the default-context read lives in exactly one
    # place.
    Export-PhaseTimingTree -Path $outputPath
}