Public/Publish-StaticSite.ps1
|
<#
.SYNOPSIS Builds and publishes a Hyde static site. .DESCRIPTION Publish-StaticSite performs a full Hyde build pipeline: - initializes site context and configuration - discovers documents and static files - prepares document metadata/front matter - synchronizes posts, pagination, and taxonomies - renders published documents - copies static assets The command returns the full Hyde build context so callers can inspect build state, discovered items, and output metadata. .PARAMETER Source Optional source path for the site. When omitted, Hyde uses default source discovery. .PARAMETER Destination Optional destination path override for generated output. .PARAMETER Environment Required environment name used for context initialization and environment-specific settings. .PARAMETER Quiet Suppresses informational output and emits only warnings/errors unless verbose output is enabled. .PARAMETER ScriptPath Internal/back-compat parameter for wrapper invocation. Not intended for normal use. .PARAMETER ModuleRoot Internal override for Hyde module root resolution. Not intended for normal use. .PARAMETER Version Internal override for reported Hyde version. Not intended for normal use. .EXAMPLE Publish-StaticSite -Source .\site -Destination .\site\_site -Environment development Builds a site from .\site into .\site\_site using the development environment. .EXAMPLE Publish-StaticSite -Source .\site -Environment production -Verbose Builds using production context and emits detailed build phase tracing. .EXAMPLE Publish-StaticSite -Source .\site -Destination .\site\_site -Environment development -WhatIf Shows file writes/copies that would occur without modifying output. .OUTPUTS HydeBuildContext Returns the populated build context for the completed run. .NOTES Supports ShouldProcess, so -WhatIf and -Confirm are honored. #> function Publish-StaticSite { [CmdletBinding(SupportsShouldProcess = $true)] param( [string]$Source, [string]$Destination, [Parameter(Mandatory = $true)] [string]$Environment, [switch]$Quiet, [string]$ScriptPath, [string]$ModuleRoot, [string]$Version ) Set-StrictMode -Version Latest $ErrorActionPreference = 'Stop' $commandInfo = Get-Command -Name $MyInvocation.MyCommand.Name -ErrorAction SilentlyContinue if ([string]::IsNullOrWhiteSpace($ModuleRoot)) { $ModuleRoot = $ExecutionContext.SessionState.Module.ModuleBase } if ([string]::IsNullOrWhiteSpace($ModuleRoot) -and $commandInfo -and $commandInfo.Module) { $ModuleRoot = $commandInfo.Module.ModuleBase } if ([string]::IsNullOrWhiteSpace($ModuleRoot)) { $ModuleRoot = Split-Path -Parent $PSScriptRoot } if ([string]::IsNullOrWhiteSpace($Version) -and $ExecutionContext.SessionState.Module.Version) { $Version = $ExecutionContext.SessionState.Module.Version.ToString() } if ([string]::IsNullOrWhiteSpace($Version) -and $commandInfo -and $commandInfo.Module -and $commandInfo.Module.Version) { $Version = $commandInfo.Module.Version.ToString() } if ([string]::IsNullOrWhiteSpace($Version) -and -not [string]::IsNullOrWhiteSpace($ModuleRoot)) { $Version = (Test-ModuleManifest -Path (Join-Path -Path $ModuleRoot -ChildPath 'Hyde.psd1')).Version.ToString() } # Build emits informational progress unless the caller explicitly asks for quiet mode. if (-not $Quiet) { $InformationPreference = 'Continue' } # Pass only the caller-provided overrides into the shared context initializer. if ($PSBoundParameters.ContainsKey('ScriptPath')) { # Keep the old wrapper/test contract working while the module becomes the primary entry point. $ModuleRoot = Split-Path -Parent $ScriptPath } $contextParameters = @{ Environment = $Environment ModuleRoot = $ModuleRoot Version = $Version } if ($PSBoundParameters.ContainsKey('Source')) { $contextParameters['Source'] = $Source } if ($PSBoundParameters.ContainsKey('Destination')) { $contextParameters['Destination'] = $Destination } Write-Verbose "Initializing Hyde build context." try { $context = initializeHydeBuildContext @contextParameters } catch { throw "Build failed while initializing site context. $($_.Exception.Message)" } Write-Information "Running HYDE version $($context.Version)." Write-Verbose "Building site from '$($context.SourcePath)' to '$($context.DestinationPath)'." Write-Verbose "Using environment '$($context.Environment)'." Write-Verbose "Loaded $($context.LoadedPlugins.Count) plugin(s)." initializeHydeLayouts -Context $context try { try { if (-not (Test-Path -LiteralPath $context.DestinationPath -PathType Container)) { if ($PSCmdlet.ShouldProcess($context.DestinationPath, 'Create destination directory')) { Write-Verbose "Creating destination directory '$($context.DestinationPath)'." [void](New-Item -Path $context.DestinationPath -ItemType Directory -Force) } else { Write-Verbose "Skipping creation of destination directory '$($context.DestinationPath)' because ShouldProcess declined it." } } else { Write-Verbose "Destination directory '$($context.DestinationPath)' already exists." } } catch { throw "Build failed while preparing destination '$($context.DestinationPath)'. $($_.Exception.Message)" } Write-Verbose "Discovering source items under '$($context.SourcePath)'." try { # Discover the source tree before any rendering starts. getHydeSourceItems -Context $context } catch { throw "Build failed while discovering source items in '$($context.SourcePath)'. $($_.Exception.Message)" } Write-Information "Processing $($context.Documents.Count) document(s) and $($context.StaticFiles.Count) static file(s)." Write-Verbose "Discovered $($context.Documents.Count) document(s) and $($context.StaticFiles.Count) static file(s)." Write-Verbose "Preparing document metadata phase." # Resolve front matter and semantic metadata for every document before any template loops read site collections. foreach ($document in $context.Documents) { try { Write-Verbose "Preparing document metadata for '$($document.RelativePath)'." initializeHydeDocument -Document $document -Context $context } catch { throw "Build failed while preparing document '$($document.SourcePath)'. $($_.Exception.Message)" } } # Post loops should see the final published, sorted post set before any page starts rendering. syncHydePosts -Context $context # Paginated listing pages are generated from the final site.posts set before rendering begins. initializeHydePagination -Context $context # Tag and category loops should also see the final published document buckets before rendering starts. syncHydeTaxonomies -Context $context Write-Verbose "Starting document rendering phase." # Documents are rendered and written first so any rendering failures stop the build early. $documentIndex = 0 $publishedDocumentCount = 0 foreach ($document in $context.Documents) { $documentIndex++ try { Write-Verbose "Rendering document $documentIndex of $($context.Documents.Count): '$($document.RelativePath)'." convertHydeDocument -Document $document -Context $context if (-not $document.Published) { Write-Verbose "Skipping unpublished document '$($document.RelativePath)'." continue } if (-not $document.WriteOutput) { Write-Verbose "Skipping output for collection document '$($document.RelativePath)' because its collection is not configured for output." continue } $documentTargetPath = Join-Path -Path $context.DestinationPath -ChildPath $document.OutputRelativePath if ($PSCmdlet.ShouldProcess($documentTargetPath, "Write document '$($document.RelativePath)'")) { Write-Verbose "Writing document '$($document.RelativePath)' to '$($document.OutputRelativePath)'." writeHydeDocument -Document $document -Context $context $publishedDocumentCount++ Write-Verbose "Finished document '$($document.RelativePath)'." } else { Write-Verbose "Skipping write of document '$($document.RelativePath)' because ShouldProcess declined it." } } catch { throw "Build failed while processing document '$($document.SourcePath)'. $($_.Exception.Message)" } } Write-Verbose "Starting static file copy phase." # Static assets are copied after document rendering. $staticFileIndex = 0 foreach ($staticFile in $context.StaticFiles) { $staticFileIndex++ try { $staticFileTargetPath = Join-Path -Path $context.DestinationPath -ChildPath $staticFile.OutputRelativePath if ($PSCmdlet.ShouldProcess($staticFileTargetPath, "Copy static file '$($staticFile.RelativePath)'")) { Write-Verbose "Copying static file $staticFileIndex of $($context.StaticFiles.Count): '$($staticFile.RelativePath)' to '$($staticFile.OutputRelativePath)'." copyHydeStaticFile -StaticFile $staticFile -Context $context Write-Verbose "Finished static file '$($staticFile.RelativePath)'." } else { Write-Verbose "Skipping copy of static file '$($staticFile.RelativePath)' because ShouldProcess declined it." } } catch { throw "Build failed while copying static file '$($staticFile.SourcePath)'. $($_.Exception.Message)" } } Write-Verbose "Build summary: wrote $publishedDocumentCount published document(s) and copied $($context.StaticFiles.Count) static file(s)." Write-Information "Finished in $(((Get-Date) - $context.Site.time).TotalSeconds.ToString('0.00')) seconds." return $context } finally { if ($context -and -not [string]::IsNullOrWhiteSpace($context.EffectiveIncludesPath) -and (Test-Path -LiteralPath $context.EffectiveIncludesPath -PathType Container)) { # Theme include fallback uses a temporary merged directory that should not outlive the build. Remove-Item -LiteralPath $context.EffectiveIncludesPath -Recurse -Force -ErrorAction SilentlyContinue } } } |