From c4c0317132f14cdf0880bdea488b32890c8b0afe Mon Sep 17 00:00:00 2001 From: Vaclav Elias <4528464+VaclavElias@users.noreply.github.com> Date: Sat, 25 Jul 2026 13:51:29 +0100 Subject: [PATCH 1/2] chore: .gitignore - exclude copied architecture folder --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index d80cc87ad..ffc0c16f8 100644 --- a/.gitignore +++ b/.gitignore @@ -365,3 +365,4 @@ FodyWeavers.xsd _site jp_tmp/* .idea +/en/contributors/engine/architecture From f4df02ea1e2570f9db48def1e796827b4c8f1511 Mon Sep 17 00:00:00 2001 From: Vaclav Elias <4528464+VaclavElias@users.noreply.github.com> Date: Sat, 25 Jul 2026 15:41:50 +0100 Subject: [PATCH 2/2] refactor(build)!: modularize and refactor docs pipeline - BuildDocs.ps1: delegate steps to new build/ scripts (Settings, Languages, ConsoleUI, FileUtility, DocFx, ArchitectureDocs, PostProcessing, Build) - Centralize paths, filenames, and tokens in Settings.ps1 - Unify interactive prompts and language selection logic - Move architecture docs import and TOC generation to ArchitectureDocs.ps1 - Move per-language build logic to Build.ps1 (overlays, warning banners) - Move DocFX invocation and API metadata to DocFx.ps1 - Move post-processing (docurl, sitemap, 404, deployment extras) to PostProcessing.ps1 - Add FileUtility.ps1 for path and file helpers - Add Languages.ps1 for language utilities - Update documentation-generation-pipeline.md for new structure --- BuildDocs.ps1 | 736 +++--------------- build/ArchitectureDocs.ps1 | 220 ++++++ build/Build.ps1 | 253 ++++++ build/Console.ps1 | 156 ++++ build/DocFx.ps1 | 89 +++ build/FileUtility.ps1 | 103 +++ build/Languages.ps1 | 72 ++ build/PostProcessing.ps1 | 173 ++++ build/Settings.ps1 | 133 ++++ .../documentation-generation-pipeline.md | 96 ++- 10 files changed, 1350 insertions(+), 681 deletions(-) create mode 100644 build/ArchitectureDocs.ps1 create mode 100644 build/Build.ps1 create mode 100644 build/Console.ps1 create mode 100644 build/DocFx.ps1 create mode 100644 build/FileUtility.ps1 create mode 100644 build/Languages.ps1 create mode 100644 build/PostProcessing.ps1 create mode 100644 build/Settings.ps1 diff --git a/BuildDocs.ps1 b/BuildDocs.ps1 index 5daa1fa70..5bffeba98 100644 --- a/BuildDocs.ps1 +++ b/BuildDocs.ps1 @@ -5,6 +5,9 @@ The script allows the user to build documentation in English or any other available language specified in the languages.json file. It provides options to build documentation in all available languages, run a local website for the documentation, or cancel the operation. If the user chooses to build the documentation, the script also prompts whether API documentation should be included unless API building is explicitly skipped. .NOTES The documentation files are expected to be in Markdown format (.md). The script uses the DocFX tool to build the documentation and optionally includes API documentation. The script generates the API documentation from C# source files using DocFX metadata and can run a local website using the DocFX serve command. This script can also be run from GitHub Actions. + + The build steps live in the build/ folder and are dot-sourced below. Each of those files is + scoped to become one class when this script is eventually ported to C#. .LINK https://github.com/stride3d/stride-docs .LINK @@ -35,700 +38,143 @@ param ( [switch]$BuildAll, [switch]$SkipApiBuilding, + [switch]$SkipPdfBuilding, [ArgumentCompleter({ [OutputType([System.Management.Automation.CompletionResult])] param([string] $CommandName,[string] $ParameterName,[string] $WordToComplete,[System.Management.Automation.Language.CommandAst] $CommandAst,[System.Collections.IDictionary] $FakeBoundParameters) return (Get-Content $PSScriptRoot\versions.json -Encoding UTF8 | ConvertFrom-Json).versions })] - [switch]$SkipPdfBuilding, - $Version = $((Get-Content $PSScriptRoot\versions.json -Encoding UTF8 | ConvertFrom-Json).versions | Sort-Object -Descending | Select-Object -First 1) + [string]$Version = $((Get-Content $PSScriptRoot\versions.json -Encoding UTF8 | ConvertFrom-Json).versions | Sort-Object -Property { [version]$_ } -Descending | Select-Object -First 1) ) -$Settings = [PSCustomObject]@{ - Version = $Version - SiteDirectory = "_site/$Version" - LocalTestHostUrl = "http://localhost:8080/$Version/en/index.html" - LanguageJsonPath = "en\languages.json" - LogPath = ".\build.log" - TempDirectory = "_tmp" - WebDirectory = "_site" - IndexFileName = "index.md" - ManualFolderName = "manual" - DocsUrl = "https://doc.stride3d.net" - EngineRepositoryUrl = "https://github.com/stride3d/stride/tree/master" -} +# Almost every path below is relative to the repository root, so anchor the working directory +# rather than depending on where the script was invoked from. +Push-Location $PSScriptRoot -# To Do fix, GitHub references, fix sitemap links to latest/en/ +try { + . "$PSScriptRoot\build\Settings.ps1" + . "$PSScriptRoot\build\Languages.ps1" + . "$PSScriptRoot\build\Console.ps1" + . "$PSScriptRoot\build\FileUtility.ps1" + . "$PSScriptRoot\build\DocFx.ps1" + . "$PSScriptRoot\build\ArchitectureDocs.ps1" + . "$PSScriptRoot\build\PostProcessing.ps1" + . "$PSScriptRoot\build\Build.ps1" -function Read-LanguageConfigurations { - return Get-Content $Settings.LanguageJsonPath -Encoding UTF8 | ConvertFrom-Json -} + $Settings = New-BuildSettings -Version $Version -RootPath $PSScriptRoot + $languages = Read-LanguageConfiguration -Settings $Settings -function Get-UserInput { - Write-Host "" - Write-Host -ForegroundColor Cyan "Please select an option:" - Write-Host "" - Write-Host -ForegroundColor Yellow " [en] Build English documentation" - foreach ($lang in $languages) { - if ($lang.Enabled -and -not $lang.IsPrimary) { - Write-Host -ForegroundColor Yellow " [$($lang.Code)] Build $($lang.Name) documentation" - } - } - Write-Host -ForegroundColor Yellow " [all] Build documentation in all available languages" - Write-Host -ForegroundColor Yellow " [r] Run local website" - Write-Host -ForegroundColor Yellow " [c] Cancel" - Write-Host "" - $validOptions = @('all', 'r', 'c') + $($languages | Select-Object -ExpandProperty Code) + $primaryLanguage = $languages | Where-Object { $_.Code -eq $Settings.PrimaryLanguageCode } | Select-Object -First 1 - while($true) - { - $userChoice = Read-Host -Prompt "Your choice" - if($validOptions -contains $userChoice) - { - return $userChoice.ToLower() - } - } - Write-Error "No valid Choice was given." -} + # Running unattended (CI) means never blocking on a prompt + $isInteractive = -not $BuildAll -function Ask-IncludeAPI { - Write-Host "" - Write-Host -ForegroundColor Cyan "Do you want to include API?" - Write-Host "" - Write-Host -ForegroundColor Yellow " [Y] Yes or ENTER" - Write-Host -ForegroundColor Yellow " [N] No" - Write-Host "" + Start-Transcript -Path $Settings.LogPath - $answer = Read-Host -Prompt "Your choice [Y, N, or ENTER (default is Y)]" - - return ($answer -ieq "y" -or $answer -eq "") -} - -function Ask-UseExistingAPI { - Write-Host "" - Write-Host -ForegroundColor Cyan "Do you want to use already generated API metadata?" - Write-Host "" - Write-Host -ForegroundColor Yellow " [Y] Yes or ENTER" - Write-Host -ForegroundColor Yellow " [N] No" - Write-Host "" - - $answer = Read-Host -Prompt "Your choice [Y, N, or ENTER (default is Y)]" - - return ($answer -ieq "y" -or $answer -eq "") -} - -function Ask-ReplaceEngineArchitectureDocs { - Write-Host "" - Write-Host -ForegroundColor Cyan "Do you want to copy and replace engine architecture docs?" - Write-Host "" - Write-Host -ForegroundColor Yellow " [Y] Yes or ENTER" - Write-Host -ForegroundColor Yellow " [N] No" - Write-Host "" - - $answer = Read-Host -Prompt "Your choice [Y, N, or ENTER (default is Y)]" - - return ($answer -ieq "y" -or $answer -eq "") -} - -function Get-RelativePath { - param ( - [string]$BasePath, - [string]$Path - ) - $baseUri = [Uri]((Resolve-Path $BasePath).Path.TrimEnd([System.IO.Path]::DirectorySeparatorChar, [System.IO.Path]::AltDirectorySeparatorChar) + [System.IO.Path]::DirectorySeparatorChar) - $pathUri = [Uri](Resolve-Path $Path).Path - return [Uri]::UnescapeDataString($baseUri.MakeRelativeUri($pathUri).ToString()) -} -function Copy-ArchitectureDocs { - Write-Host -ForegroundColor Green "Copying architecture documentation from the main engine repository..." - $architectureFolder = "en/contributors/engine/architecture" - $engineDocsFolder = Join-Path $PSScriptRoot "..\stride\docs" - - # Return if the repository isn't available - if (-not (Test-Path $engineDocsFolder)) { - Write-Host -ForegroundColor Yellow "Skipping copying architecture documentation, can't find the main engine repository. Make sure the stride directory is located next to stride-docs." - return - } - - # Remove old files - if (Test-Path $architectureFolder) { - Remove-Item $architectureFolder/* -Recurse -Verbose - } - - # Copy files - Get-ChildItem $engineDocsFolder -Force | ForEach-Object { - Copy-Item $_.FullName (Join-Path $architectureFolder $_.Name) -Recurse -Verbose - } - - # Post-processing files - $files = Get-ChildItem $architectureFolder -Recurse -Force - foreach ($file in $files) { - $relativePath = Get-RelativePath $architectureFolder $file.FullName - - # Replacing content - if (!$file.PSIsContainer) { - $relativePathSlashes = ([regex]::Matches($relativePath, "/" )).count - $wrongRelative = "]($("../" * $relativePathSlashes)" - - # Go over all lines in the file - $content = Get-Content $file.FullName - for ($i = 0; $i -lt $content.Length; $i++) { - # Look for links - $linkMatches = [regex]::Matches($content[$i], "\[[^\[\]]*?\]\([^\(\)]*?\)") - foreach ($match in $linkMatches) { - # Replace relative links to files from the repo with non-relative - $content[$i] = $content[$i].Replace($wrongRelative, "]($($Settings.EngineRepositoryUrl)/") - - # Replace links to README.md files with index.md - if (!$content[$i].Contains("https://") -and $content[$i].Contains("README")) { - $content[$i] = $content[$i].Replace("README", "index") - } - } - } - - ## Apply changes to content - $content | Set-Content $file.FullName - } - - # Rename README.md files to index.md - if ($file.ToString().ToLower().Contains("readme.md")) { - $renamedLocation = "$($file.FullName | Split-Path)/index.md" - Move-Item $file.FullName $renamedLocation - } + if (-not $primaryLanguage) { + Exit-WithError -Message "No language with code '$($Settings.PrimaryLanguageCode)' found in $($Settings.LanguageJsonPath)." -Interactive:$isInteractive } - # Copy root index file - $sectionIndex = "en/contributors/engine/architecture-index.md" - if (Test-Path $sectionIndex) { - Copy-Item $sectionIndex "$architectureFolder/index.md" + # Decide what to build + if ($BuildAll) { + $isAllLanguages = $true + $isPrimaryLanguage = $false + $selectedLanguage = $null + $buildApi = -not $SkipApiBuilding + $reuseApi = $false + $buildArchitecture = $true } -} - -function Get-ArchitectureDocsTocChildren { - - - param ([string]$Directory) - - return @(Get-ChildItem -LiteralPath $Directory -Force | - Where-Object { $_.PSIsContainer -or $_.Extension -ieq ".md" } | - Where-Object { $_.Name -ine "index.md" -and $_.Name -ine "toc.yml" } | - Sort-Object @{ Expression = { if ($_.PSIsContainer) { 0 } else { 1 } } }, Name) - } - -function Get-ArchitectureDocsTocTitle { - param ([System.IO.FileSystemInfo]$Item) - - $title = [System.IO.Path]::GetFileNameWithoutExtension($Item.Name).Replace("-", " ") - if ($title.Length -gt 0) { - $title = "$($title[0].ToString().ToUpper())$($title.Substring(1))" - } + else { + $userInput = Read-BuildOption -Language $languages - $titleSource = $Item.FullName - if ($Item.PSIsContainer) { - $titleSource = Join-Path $Item.FullName "index.md" + if ($userInput -eq 'c') { + Write-Host -ForegroundColor Red "Operation canceled by user." + Stop-BuildTranscript + Wait-ForUserExit -Interactive:$isInteractive + return } - if (Test-Path $titleSource) { - $heading = Select-String -LiteralPath $titleSource -Encoding UTF8 -Pattern '^# (.+?)(?:\s[-\u2014]\s.*)?$' | Select-Object -First 1 - if ($null -ne $heading) { - $title = $heading.Matches[0].Groups[1].Value.Trim().Replace("&", "&") - } - } - - return $title.Replace("'", "''") - + if ($userInput -eq 'r') { + Start-LocalWebsite -Settings $Settings + return } -function Add-ArchitectureDocsTocItems { - param ( - [string]$Directory, - [string]$RootDirectory, - [int]$IndentLevel, - [System.Collections.Generic.List[string]]$Lines - ) + # Read-BuildOption only returns 'all' or an enabled language code at this point + $isAllLanguages = $userInput -eq 'all' + $isPrimaryLanguage = $userInput -eq $Settings.PrimaryLanguageCode + $selectedLanguage = Find-TranslatableLanguage -Language $languages -Code $userInput - foreach ($item in Get-ArchitectureDocsTocChildren $Directory) { - $indent = " " * $IndentLevel - $title = Get-ArchitectureDocsTocTitle $item - $Lines.Add("$indent- name: '$title'") - if ($item.PSIsContainer) { - $indexPath = Join-Path $item.FullName "index.md" - if (Test-Path $indexPath) { - $Lines.Add("$indent href: $(Get-RelativePath $RootDirectory $indexPath)") - } - - $children = Get-ArchitectureDocsTocChildren $item.FullName - if ($children.Count -gt 0) { - $Lines.Add("$indent items:") - Add-ArchitectureDocsTocItems $item.FullName $RootDirectory ($IndentLevel + 1) $Lines - } + if ($SkipApiBuilding) { + $buildApi = $false + $reuseApi = $false } else { - $Lines.Add("$indent href: $(Get-RelativePath $RootDirectory $item.FullName)") + $buildApi = Confirm-Choice "Do you want to include API?" + $reuseApi = $buildApi -and (Test-ApiMetadata -Settings $Settings) -and (Confirm-Choice "Do you want to use already generated API metadata?") } - } -} - -function Generate-ArchitectureDocsToc { - $architectureFolder = "en/contributors/engine/architecture" - $tocLocation = Join-Path $architectureFolder "toc.yml" - Write-Host -ForegroundColor Green "Generating architecture docs toc.yml..." - $lines = [System.Collections.Generic.List[string]]::new() - Add-ArchitectureDocsTocItems $architectureFolder $architectureFolder 0 $lines - [System.IO.File]::WriteAllLines((Join-Path $PSScriptRoot $tocLocation), $lines, [System.Text.UTF8Encoding]::new($false)) -} -function Copy-ExtraItems { - - Write-Host -ForegroundColor Yellow "Copying versions.json into $($Settings.WebDirectory)/" - Write-Host "" - Copy-Item versions.json "$($Settings.WebDirectory)/" - - Write-Host -ForegroundColor Yellow "Copying web.config into $($Settings.WebDirectory)/" - Write-Host "" - Copy-Item web.config "$($Settings.WebDirectory)/" - - Write-Host -ForegroundColor Yellow "Updating web.config" - Write-Host "" - - $webConfig = "$($Settings.WebDirectory)/web.config" - - $content = Get-Content $webConfig -Encoding UTF8 - - $content = $content -replace "%deployment_version%", $Settings.Version - - $content | Set-Content -Encoding UTF8 $webConfig - - Write-Host -ForegroundColor Green "Updating web.config completed." - Write-Host "" - - # This is needed for Stride Launcher, which loads Release Notes - Write-Host -ForegroundColor Yellow "Copying ReleaseNotes.md into $($Settings.SiteDirectory)/en/ReleaseNotes/" - Write-Host "" - Copy-Item en/ReleaseNotes/ReleaseNotes.md "$($Settings.SiteDirectory)/en/ReleaseNotes/" - Write-Host -ForegroundColor Yellow "Copying robots.txt into $($Settings.WebDirectory)/" - Write-Host "" - Copy-Item robots.txt "$($Settings.WebDirectory)/" -} - -function Start-LocalWebsite { - Write-Host -ForegroundColor Green "Running local website..." - Write-Host -ForegroundColor Green "Navigate manually to non English website, if you didn't build English documentation." - Stop-Transcript - - New-Item -ItemType Directory -Verbose -Force -Path $Settings.WebDirectory | Out-Null - - Set-Location $Settings.WebDirectory - - Start-Process -FilePath $Settings.LocalTestHostUrl - - docfx serve - - Set-Location .. -} - -function Generate-APIDoc { - Write-Host -ForegroundColor Green "Generating API documentation..." - - Write-Host "" - - # Build metadata from C# source, docfx runs dotnet restore - docfx metadata en/docfx.json | Write-Host - - return $LastExitCode -} - -function Remove-APIDoc { - Write-Host "" - Write-Host -ForegroundColor Green "Erasing API documentation..." - $APIDocPath = "en/api/.manifest" - if (Test-Path $APIDocPath) { - Remove-Item en/api/*yml -recurse -Verbose - Remove-Item $APIDocPath -Verbose - } else{ - Write-Warning "Could not delete APIDoc. The Path $APIDocPath does not exist or is not valid." + # Architecture docs are independent of the API, so this is asked either way + $buildArchitecture = Confirm-Choice "Do you want to copy and replace engine architecture docs?" } -} - -function Build-EnglishDoc { - - $outputDirectory = "$($Settings.SiteDirectory)/en" - - Write-Host -ForegroundColor Yellow "Start building English documentation. Output: $($outputDirectory)" - - Write-Host "" - - # Output to both build.log and console - docfx build en/docfx.json -o $outputDirectory | Write-Host - - Build-EnglishPdf -SkipBuilding $SkipPdfBuilding - - return $LastExitCode -} -function Build-EnglishPdf -{ - param ( - $SkipBuilding - ) - if(!$SkipBuilding) - { - # Build pdf files - docfx pdf en/docfx.json -o $outputDirectory | Write-Host + # Generate API doc + if ($reuseApi) { + Write-Step "Generating API documentation from existing metadata..." } -} - -function Build-NonEnglishDoc { - param ( - $SelectedLanguage - ) - - if ($SelectedLanguage -and $SelectedLanguage.Code -ne 'en') { + elseif ($buildApi) { + $exitCode = New-ApiMetadata -Settings $Settings - Write-Host "-------------------------------------------------------------------------------" - Write-Host "" - Write-Host -ForegroundColor Yellow "Start building $($SelectedLanguage.Name) documentation." - Write-Host "" - - $langFolder = "$($SelectedLanguage.Code)$($Settings.TempDirectory)" - - if (Test-Path $langFolder) { - Remove-Item $langFolder/* -recurse -Verbose - } - else { - $discard = New-Item -Path $langFolder -ItemType Directory -Verbose + if ($exitCode -ne 0) { + Exit-WithError -Message "Failed to generate API metadata. ExitCode: $exitCode" -ExitCode $exitCode -Interactive:$isInteractive } - - # Copy all files from en folder to the selected language folder, this way we can keep en files that are not translated - Copy-Item en/* -Recurse $langFolder -Force - - # Get all previously copied en files from the selected language folder - $files = Get-ChildItem "$langFolder/$($Settings.ManualFolderName)/*.md" -Recurse -Force - - Write-Host "Start write files:" - - # Mark files as not translated if they are not a toc.md file - foreach ($file in $files) - { - if($file.ToString().Contains("toc.md")) { - continue; - } - - $data = Get-Content $file -Encoding UTF8 - for ($i = 0; $i -lt $data.Length; $i++) - { - $line = $data[$i]; - if ($line.length -le 0) - { - Write-Host $file - - $data[$i]="> [!WARNING]`r`n> " + $SelectedLanguage.NotTranslatedMessage + "`r`n" - - $data | Out-File -Encoding UTF8 $file - - break - } - } - } - - Write-Host "End write files" - $indexFile = $Settings.IndexFileName - - # overwrite en manual page with translated manual page - if (Test-Path ($SelectedLanguage.Code + "/" + $indexFile)) { - Copy-Item ($SelectedLanguage.Code + "/" + $indexFile) $langFolder -Force - } - else { - Write-Warning "$($SelectedLanguage.Code)/"+ $indexFile +" not found. English version will be used." - } - - # overwrite en manual pages with translated manual pages - if (Test-Path ($SelectedLanguage.Code + "/" + $Settings.ManualFolderName)) { - Copy-Item ($SelectedLanguage.Code + "/" + $Settings.ManualFolderName) -Recurse -Destination $langFolder -Force - } - else { - Write-Warning "$($SelectedLanguage.Code)/$($Settings.ManualFolderName) not found." - } - - # we copy the docfx.json file from en folder to the selected language folder, so we can keep the same settings and maitain just one docfx.json file - Copy-Item en/docfx.json $langFolder -Force - - $SiteDir = $Settings.SiteDirectory - - # we replace the en folder with the selected language folder in the docfx.json file - (Get-Content $langFolder/docfx.json) -replace "$SiteDir/en","$SiteDir/$($SelectedLanguage.Code)" | Set-Content -Encoding UTF8 $langFolder/docfx.json - - $outputDirectory = "$($Settings.SiteDirectory)/$($SelectedLanguage.Code)" - - docfx build $langFolder/docfx.json -o $outputDirectory | Write-Host - - if (!$BuildAll) { - Remove-Item $langFolder -Recurse -Verbose - } - - PostProcessing-DocFxDocUrl -SelectedLanguage $SelectedLanguage - - Write-Host -ForegroundColor Green "$($SelectedLanguage.Name) documentation built." - - return $LastExitCode } -} - -function Build-AllLanguagesDocs { - param ( - [array]$Languages - ) - - foreach ($lang in $Languages) { - if ($lang.Enabled -and -not $lang.IsPrimary) { - - Build-NonEnglishDoc -SelectedLanguage $lang - - if ($exitCode -ne 0) - { - Write-Error "Failed to build $($SelectedLanguage.Name) documentation. ExitCode: $exitCode" - Stop-Transcript - return $exitCode - } - } + else { + Remove-ApiDocumentation -Settings $Settings } -} - -function PostProcessing-DocFxDocUrl { -<# -.SYNOPSIS - DocFX generates GitHub link based on the temp _tmp folder, which we need to fix to correct GitHub links. This function performs the needed adjustments. -.DESCRIPTION - This function takes a selected language as input and iterates through the relevant HTML and markdown files. It corrects the meta tag "docfx:docurl" and anchor tags to reflect the correct GitHub URL by replacing the temporary folder path. -.PARAMETER SelectedLanguage - The Language to find the relevant HTML and markdown Files -.NOTES - 1. This function assumes that the folder structure and naming conventions meet the specified conditions. - 2. Progress is displayed interactively, and is suppressed in non-interactive sessions such as CI/CD pipelines. -#> - param ( - $SelectedLanguage - ) - - $translatedFiles = Get-ChildItem "$($SelectedLanguage.Code)/*.md" -Recurse -Force - - # Get a list of all HTML files in the _site/ directory - $htmlFiles = Get-ChildItem "$($Settings.SiteDirectory)/$($SelectedLanguage.Code)/*.html" -Recurse - - # Get the relative paths of the translated files - $relativeTranslatedFilesPaths = $translatedFiles | ForEach-Object { $_.FullName.Replace((Resolve-Path $SelectedLanguage.Code).Path + '\', '') } - - Write-Host -ForegroundColor Yellow "Post-processing docfx:docurl in $($htmlFiles.Count) files..." - for ($i = 0; $i -lt $htmlFiles.Count; $i++) { - $htmlFile = $htmlFiles[$i] - # Get the relative path of the HTML file - $relativeHtmlPath = $htmlFile.FullName.Replace((Resolve-Path "$($Settings.SiteDirectory)/$($SelectedLanguage.Code)").Path + '\', '').Replace('.html', '.md') - - # Read the content of the HTML file - $content = Get-Content $htmlFile -Encoding UTF8 - - # Define a regex pattern to match the meta tag with name="docfx:docurl" - $pattern = '()' - - # Define a regex pattern to match the href attribute in the tags - $pattern2 = '()' - - # Check if the HTML file is from the $translatedFiles collection, if so, we will update the path to the - # existing file in GitHub - if ($relativeTranslatedFilesPaths -contains $relativeHtmlPath) { - # Replace /_tmp/ with // in the content - $content = $content -replace $pattern, "`${1}/$($SelectedLanguage.Code)/`${3}" - $content = $content -replace $pattern2, "`${1}/$($SelectedLanguage.Code)/`${3}" - } else { - # Replace /_tmp/ with /en/ in the content - $content = $content -replace $pattern, '${1}/en/${3}' - $content = $content -replace $pattern2, '${1}/en/${3}' - } - - # Write the updated content back to the HTML file - $content | Set-Content -Encoding UTF8 $htmlFile - - # Check if the script is running in an interactive session before writing progress - # We don't want to write progress when running in a non-interactive session, such as in a build pipeline - if ($host.UI.RawUI) { - Write-Progress -Activity "Processing files" -Status "$i of $($htmlFiles.Count) processed" -PercentComplete (($i / $htmlFiles.Count) * 100) - } + # Engine architecture docs + if ($buildArchitecture) { + Copy-ArchitectureDocs -Settings $Settings + New-ArchitectureDocsToc -Settings $Settings } + Write-Step "Generating documentation..." + Write-Warning "Note that when building docs without API, you will get UidNotFound warnings and invalid references warnings" Write-Host "" - Write-Host -ForegroundColor Green "Post-processing completed." - Write-Host "" -} - -# we need to update all urls to /latest/en -function PostProcessing-FixingSitemap { - Write-Host -ForegroundColor Yellow "Post-processing sitemap.xml, adding latest/en to url" - Write-Host "" - - $sitemapFile = "$($Settings.SiteDirectory)/en/sitemap.xml" - - # Read the content of the sitemap.xml file with UTF8 encoding - $content = Get-Content $sitemapFile -Encoding UTF8 - # Replace DocsUrl with DocsUrl + latest/en - $content = $content -replace $Settings.DocsUrl, "$($Settings.DocsUrl)/latest/en" + if ($isPrimaryLanguage -or $isAllLanguages) { + $exitCode = Build-EnglishDoc -Settings $Settings -PrimaryLanguage $primaryLanguage -SkipPdf:$SkipPdfBuilding - # Write the updated content back to the sitemap.xml file with UTF8 encoding - $content | Set-Content -Encoding UTF8 $sitemapFile - - Write-Host -ForegroundColor Green "Post-processing sitemap.xml completed." - Write-Host "" -} - -function PostProcessing-Fixing404AbsolutePath { - Write-Host -ForegroundColor Yellow "Post-processing 404.html, adding version/en to url" - Write-Host "" - - $file404 = "$($Settings.SiteDirectory)/en/404.html" + if ($exitCode -ne 0) { + Exit-WithError -Message "Failed to build $($primaryLanguage.Name) documentation. ExitCode: $exitCode" -ExitCode $exitCode -Interactive:$isInteractive + } - $content = Get-Content $file404 -Encoding UTF8 + Update-Sitemap -Settings $Settings - $keysToReplace = @("favicon.ico", "public/docfx.min.css", "public/main.css", "toc.html", "media/stride-logo-red.svg") + Update-NotFoundPage -Settings $Settings - foreach ($key in $keysToReplace) { - $replacement = "/$($Settings.Version)/en/$key" - $content = $content -replace $key, $replacement + Copy-ExtraItem -Settings $Settings } - $content = $content -replace "./public/main.js", "/$($Settings.Version)/en/public/main.js" - $content = $content -replace "./public/docfx.min.js", "/$($Settings.Version)/en/public/docfx.min.js" - $content = $content -replace '', '' - - $content | Set-Content -Encoding UTF8 $file404 - - Write-Host -ForegroundColor Green "Post-processing 404.html completed." - Write-Host "" -} - -# Main script execution starts here - -$languages = Read-LanguageConfigurations - -Start-Transcript -Path $Settings.LogPath - -[bool]$isAllLanguages = $false - -if ($BuildAll) -{ - $isAllLanguages = $true - $API = -not $SkipApiBuilding - $ReuseAPI = $false - $engineArchitecture = $true -} -else { - $userInput = Get-UserInput - - [bool]$isEnLanguage = $userInput -ieq "en" - [bool]$shouldRunLocalWebsite = $userInput -ieq "r" - [bool]$isCanceled = $userInput -ieq "c" - $isAllLanguages = $userInput -ieq "all" - - # Check if user input matches any non-English language build - $selectedLanguage = $languages | Where-Object { $_.Code -eq $userInput -and $_.Enabled -and -not $_.IsPrimary } - - if ($selectedLanguage) - { - [bool]$shouldBuildSelectedLanguage = $true + # Build non-English language if selected or build all languages if selected + if ($isAllLanguages) { + $exitCode = Build-AllLanguagesDoc -Settings $Settings -Language $languages -KeepTempFolder:$BuildAll } - - # Ask if the user wants to run additional steps - if ($isEnLanguage -or $isAllLanguages -or $shouldBuildSelectedLanguage) - { - if ($SkipApiBuilding) - { - $API = $false - $ReuseAPI = $false - } - else - { - $API = Ask-IncludeAPI - - if ($API) - { - # Check for .yml files - $ymlFiles = Get-ChildItem -Path "en/api/" -Filter "*.yml" - - if ($ymlFiles.Count -gt 0) - { - $ReuseAPI = Ask-UseExistingAPI - } - } - - $engineArchitecture = Ask-ReplaceEngineArchitectureDocs - } - } elseif ($isCanceled) { - Write-Host -ForegroundColor Red "Operation canceled by user." - Stop-Transcript - Read-Host -Prompt "Press ENTER key to exit..." - return - } elseif ($shouldRunLocalWebsite) { - Start-LocalWebsite - return + elseif ($selectedLanguage) { + $exitCode = Build-NonEnglishDoc -Settings $Settings -SelectedLanguage $selectedLanguage -KeepTempFolder:$BuildAll } -} - -# Generate API doc -if ($ReuseAPI) -{ - Write-Host -ForegroundColor Green "Generating API documentation from existing mete data..." -} elseif ($API) -{ - $exitCode = Generate-APIDoc - - if($exitCode -ne 0) - { - Write-Error "Failed to generate API metadata. ExitCode: $exitCode" - Stop-Transcript - Read-Host -Prompt "Press any ENTER to exit..." - return $exitCode + else { + $exitCode = 0 } -} else { - Remove-APIDoc -} - -# Engine architecture docs -if ($engineArchitecture) { - Copy-ArchitectureDocs - Generate-ArchitectureDocsToc -} - -Write-Host -ForegroundColor Green "Generating documentation..." -Write-Host "" -Write-Warning "Note that when building docs without API, you will get UidNotFound warnings and invalid references warnings" -Write-Host "" -if ($isEnLanguage -or $isAllLanguages) -{ - $exitCode = Build-EnglishDoc - - if ($exitCode -ne 0) - { - Write-Error "Failed to build English documentation. ExitCode: $exitCode" - Stop-Transcript - Read-Host -Prompt "Press any ENTER to exit..." - return $exitCode - } - - PostProcessing-FixingSitemap + if ($exitCode -ne 0) { + Exit-WithError -Message "Failed to build translated documentation. ExitCode: $exitCode" -ExitCode $exitCode -Interactive:$isInteractive + } - PostProcessing-Fixing404AbsolutePath + Stop-BuildTranscript - Copy-ExtraItems + Wait-ForUserExit -Interactive:$isInteractive } - -# Build non-English language if selected or build all languages if selected -if ($isAllLanguages) { - Build-AllLanguagesDocs -Languages $languages -} elseif ($selectedLanguage) { - Build-NonEnglishDoc -SelectedLanguage $selectedLanguage +finally { + Pop-Location } - -Stop-Transcript - -Read-Host -Prompt "Press any ENTER to exit..." diff --git a/build/ArchitectureDocs.ps1 b/build/ArchitectureDocs.ps1 new file mode 100644 index 000000000..b6b102df3 --- /dev/null +++ b/build/ArchitectureDocs.ps1 @@ -0,0 +1,220 @@ +<# +.SYNOPSIS + Imports the engine architecture documentation from the sibling stride repository and + generates its table of contents. +.NOTES + This file maps onto a future C# ArchitectureDocsImporter plus TocGenerator. The TOC + generation is already written in an almost-C# style and is the easiest piece to port. + + Known issue, deliberately left as-is: the relative-link rewriting in Copy-ArchitectureDocs + miscounts directory depth and rewrites some in-site links to GitHub URLs. Fixing it needs a + proper per-link rewrite rather than whole-line string replacement, which is out of scope + for this refactor. +#> + +function Copy-ArchitectureDocs { + <# + .SYNOPSIS + Mirrors stride/docs into the architecture folder, rewriting links and README file names. + #> + [CmdletBinding()] + param ( + [Parameter(Mandatory)] + [PSCustomObject]$Settings + ) + + Write-Host -ForegroundColor Green "Copying architecture documentation from the main engine repository..." + + $architectureFolder = $Settings.ArchitectureDirectory + $engineDocsFolder = Join-Path $Settings.RootPath $Settings.EngineDocsDirectory + + # Return if the repository isn't available + if (-not (Test-Path $engineDocsFolder)) { + Write-Host -ForegroundColor Yellow "Skipping copying architecture documentation, can't find the main engine repository. Make sure the stride directory is located next to stride-docs." + return + } + + # Remove old files + if (Test-Path $architectureFolder) { + Remove-Item "$architectureFolder/*" -Recurse -Force -Verbose + } + + # Copy files + Get-ChildItem $engineDocsFolder -Force | ForEach-Object { + Copy-Item $_.FullName (Join-Path $architectureFolder $_.Name) -Recurse -Verbose + } + + # Post-processing files. The list is captured up front because the loop renames files. + $files = @(Get-ChildItem $architectureFolder -Recurse -Force) + + foreach ($file in $files) { + if (-not $file.PSIsContainer) { + Update-ArchitectureDocsLink -Settings $Settings -File $file -RootDirectory $architectureFolder + } + + # Rename README.md files to index.md + if ($file.Name -ieq $Settings.ReadmeFileName) { + $renamedLocation = Join-Path ($file.FullName | Split-Path) $Settings.IndexFileName + Move-Item $file.FullName $renamedLocation -Force + } + } + + # Copy root index file + if (Test-Path $Settings.ArchitectureIndexFile) { + Copy-Item $Settings.ArchitectureIndexFile (Join-Path $architectureFolder $Settings.IndexFileName) + } +} + +function Update-ArchitectureDocsLink { + <# + .SYNOPSIS + Rewrites links in an imported architecture file. + .DESCRIPTION + Links that point outside the imported docs folder are redirected at the engine + repository, and README references become index references. + #> + [CmdletBinding()] + param ( + [Parameter(Mandatory)][PSCustomObject]$Settings, + [Parameter(Mandatory)][System.IO.FileSystemInfo]$File, + [Parameter(Mandatory)][string]$RootDirectory + ) + + $relativePath = Get-RelativePath -BasePath $RootDirectory -Path $File.FullName + $relativePathSlashes = ([regex]::Matches($relativePath, '/')).Count + $wrongRelative = "]($('../' * $relativePathSlashes)" + + $content = @(Get-Content $File.FullName -Encoding UTF8) + + for ($i = 0; $i -lt $content.Length; $i++) { + # Only touch lines that actually contain a markdown link + if (-not [regex]::IsMatch($content[$i], '\[[^\[\]]*?\]\([^\(\)]*?\)')) { + continue + } + + # Replace relative links to files from the repo with non-relative + $content[$i] = $content[$i].Replace($wrongRelative, "]($($Settings.EngineRepositoryUrl)/") + + # Replace links to README.md files with index.md + if (!$content[$i].Contains("https://") -and $content[$i].Contains("README")) { + $content[$i] = $content[$i].Replace("README", "index") + } + } + + Set-Utf8Content -Path $File.FullName -Content $content +} + +function Get-ArchitectureDocsTocChild { + <# + .SYNOPSIS + The folders and markdown files that become TOC entries, folders first then alphabetical. + #> + [CmdletBinding()] + [OutputType([System.IO.FileSystemInfo[]])] + param ( + [Parameter(Mandatory)][PSCustomObject]$Settings, + [Parameter(Mandatory)][string]$Directory + ) + + return @(Get-ChildItem -LiteralPath $Directory -Force | + Where-Object { $_.PSIsContainer -or $_.Extension -ieq '.md' } | + Where-Object { $_.Name -ine $Settings.IndexFileName -and $_.Name -ine $Settings.TocFileName } | + Sort-Object @{ Expression = { if ($_.PSIsContainer) { 0 } else { 1 } } }, Name) +} + +function Get-ArchitectureDocsTocTitle { + <# + .SYNOPSIS + The display title for a TOC entry, taken from the document's first heading where + available and otherwise derived from the file name. + #> + [CmdletBinding()] + [OutputType([string])] + param ( + [Parameter(Mandatory)][PSCustomObject]$Settings, + [Parameter(Mandatory)][System.IO.FileSystemInfo]$Item + ) + + $title = [System.IO.Path]::GetFileNameWithoutExtension($Item.Name).Replace('-', ' ') + if ($title.Length -gt 0) { + $title = "$($title[0].ToString().ToUpper())$($title.Substring(1))" + } + + $titleSource = $Item.FullName + if ($Item.PSIsContainer) { + $titleSource = Join-Path $Item.FullName $Settings.IndexFileName + } + + if (Test-Path $titleSource) { + # The em dash below is written as a \u escape to keep this file pure ASCII, + # because Windows PowerShell reads BOM-less .ps1 files as ANSI and would + # otherwise corrupt a literal em dash and break the match. + $heading = Select-String -LiteralPath $titleSource -Encoding UTF8 -Pattern '^# (.+?)(?:\s[-\u2014]\s.*)?$' | Select-Object -First 1 + + if ($null -ne $heading) { + $title = $heading.Matches[0].Groups[1].Value.Trim().Replace('&', '&') + } + } + + # Escape single quotes for the single-quoted YAML scalar the caller emits + return $title.Replace("'", "''") +} + +function Add-ArchitectureDocsTocItem { + <# + .SYNOPSIS + Recursively appends YAML TOC lines for everything under $Directory. + #> + [CmdletBinding()] + param ( + [Parameter(Mandatory)][PSCustomObject]$Settings, + [Parameter(Mandatory)][string]$Directory, + [Parameter(Mandatory)][string]$RootDirectory, + [Parameter(Mandatory)][int]$IndentLevel, + [Parameter(Mandatory)][AllowEmptyCollection()][System.Collections.Generic.List[string]]$Line + ) + + foreach ($item in Get-ArchitectureDocsTocChild -Settings $Settings -Directory $Directory) { + $indent = ' ' * $IndentLevel + $title = Get-ArchitectureDocsTocTitle -Settings $Settings -Item $item + + $Line.Add("$indent- name: '$title'") + + if (-not $item.PSIsContainer) { + $Line.Add("$indent href: $(Get-RelativePath -BasePath $RootDirectory -Path $item.FullName)") + continue + } + + $indexPath = Join-Path $item.FullName $Settings.IndexFileName + if (Test-Path $indexPath) { + $Line.Add("$indent href: $(Get-RelativePath -BasePath $RootDirectory -Path $indexPath)") + } + + $children = Get-ArchitectureDocsTocChild -Settings $Settings -Directory $item.FullName + if ($children.Count -gt 0) { + $Line.Add("$indent items:") + Add-ArchitectureDocsTocItem -Settings $Settings -Directory $item.FullName -RootDirectory $RootDirectory -IndentLevel ($IndentLevel + 1) -Line $Line + } + } +} + +function New-ArchitectureDocsToc { + <# + .SYNOPSIS + Writes toc.yml for the imported architecture documentation. + #> + [CmdletBinding()] + param ( + [Parameter(Mandatory)] + [PSCustomObject]$Settings + ) + + Write-Host -ForegroundColor Green "Generating architecture docs $($Settings.TocFileName)..." + + $architectureFolder = $Settings.ArchitectureDirectory + $lines = [System.Collections.Generic.List[string]]::new() + + Add-ArchitectureDocsTocItem -Settings $Settings -Directory $architectureFolder -RootDirectory $architectureFolder -IndentLevel 0 -Line $lines + + Set-Utf8Content -Path (Join-Path $architectureFolder $Settings.TocFileName) -Content $lines +} diff --git a/build/Build.ps1 b/build/Build.ps1 new file mode 100644 index 000000000..76f8163d4 --- /dev/null +++ b/build/Build.ps1 @@ -0,0 +1,253 @@ +<# +.SYNOPSIS + The documentation build steps for the primary and translated languages. +.NOTES + This file maps onto a future C# BuildPipeline. Every function returns a DocFX exit code so + that failures propagate instead of relying on the ambient $LASTEXITCODE. +#> + +function Build-EnglishDoc { + <# + .SYNOPSIS + Builds the primary language site and, unless skipped, its PDF. + .OUTPUTS + The DocFX exit code. Non-zero means the build failed. + #> + [CmdletBinding()] + [OutputType([int])] + param ( + [Parameter(Mandatory)] + [PSCustomObject]$Settings, + + [Parameter(Mandatory)] + [PSCustomObject]$PrimaryLanguage, + + [switch]$SkipPdf + ) + + $outputDirectory = Get-LanguageOutputPath -Settings $Settings -Code $Settings.PrimaryLanguageCode + + Write-Step "Start building $($PrimaryLanguage.Name) documentation. Output: $outputDirectory" -Color Yellow + + $exitCode = Invoke-DocFx -Argument @('build', $Settings.DocFxConfigFile, '-o', $outputDirectory) + + # A successful PDF run must not mask a failed site build + if ($exitCode -ne 0) { + return $exitCode + } + + if ($SkipPdf) { + return 0 + } + + return Build-EnglishPdf -Settings $Settings -OutputDirectory $outputDirectory +} + +function Build-EnglishPdf { + <# + .SYNOPSIS + Builds the PDF version of the primary language documentation. + #> + [CmdletBinding()] + [OutputType([int])] + param ( + [Parameter(Mandatory)] + [PSCustomObject]$Settings, + + [Parameter(Mandatory)] + [string]$OutputDirectory + ) + + return Invoke-DocFx -Argument @('pdf', $Settings.DocFxConfigFile, '-o', $OutputDirectory) +} + +function Build-NonEnglishDoc { + <# + .SYNOPSIS + Builds a translated site from a temporary overlay folder. + .DESCRIPTION + The primary language content is copied into _tmp first so untranslated pages fall + back to English, each fallback page is marked with a warning banner, and the translated + index page and manual are then copied over the top. + .OUTPUTS + The DocFX exit code. Non-zero means the build failed. + #> + [CmdletBinding()] + [OutputType([int])] + param ( + [Parameter(Mandatory)] + [PSCustomObject]$Settings, + + [Parameter(Mandatory)] + [PSCustomObject]$SelectedLanguage, + + [switch]$KeepTempFolder + ) + + $code = $SelectedLanguage.Code + + if ($code -eq $Settings.PrimaryLanguageCode) { + Write-Warning "Skipping '$code'; it is the primary language and is built separately." + return 0 + } + + Write-Host "-------------------------------------------------------------------------------" + Write-Host "" + Write-Step "Start building $($SelectedLanguage.Name) documentation." -Color Yellow + + $langFolder = Get-LanguageTempPath -Settings $Settings -Code $code + $sourceFolder = Get-LanguageSourcePath -Settings $Settings -Code $code + + if (Test-Path $langFolder) { + Remove-Item "$langFolder/*" -Recurse -Verbose + } + else { + New-Item -Path $langFolder -ItemType Directory -Verbose | Out-Null + } + + # Copy all files from the primary language folder, so untranslated pages still render + Copy-Item "$($Settings.SourceDirectory)/*" -Recurse $langFolder -Force + + Add-NotTranslatedWarning -Settings $Settings -SelectedLanguage $SelectedLanguage -TempFolder $langFolder + + # Overwrite the primary index page with the translated one + $translatedIndex = "$sourceFolder/$($Settings.IndexFileName)" + if (Test-Path $translatedIndex) { + Copy-Item $translatedIndex $langFolder -Force + } + else { + Write-Warning "$translatedIndex not found. English version will be used." + } + + # Overwrite the primary manual pages with the translated ones + $translatedManual = "$sourceFolder/$($Settings.ManualFolderName)" + if (Test-Path $translatedManual) { + Copy-Item $translatedManual -Recurse -Destination $langFolder -Force + } + else { + Write-Warning "$translatedManual not found." + } + + # Reuse the primary docfx.json so there is only one set of settings to maintain + Copy-Item $Settings.DocFxConfigFile $langFolder -Force + + $docFxConfigName = Split-Path -Leaf $Settings.DocFxConfigFile + $outputDirectory = Get-LanguageOutputPath -Settings $Settings -Code $code + + $exitCode = Invoke-DocFx -Argument @('build', "$langFolder/$docFxConfigName", '-o', $outputDirectory) + + if (-not $KeepTempFolder) { + Remove-Item $langFolder -Recurse -Verbose + } + + if ($exitCode -ne 0) { + return $exitCode + } + + Update-DocFxDocUrl -Settings $Settings -SelectedLanguage $SelectedLanguage + + Write-Host -ForegroundColor Green "$($SelectedLanguage.Name) documentation built." + + return $exitCode +} + +function Add-NotTranslatedWarning { + <# + .SYNOPSIS + Prefixes each copied manual page with a banner saying it has not been translated. + .DESCRIPTION + The banner replaces the first blank line, which in these documents sits between the + title and the body. + #> + [CmdletBinding()] + param ( + [Parameter(Mandatory)][PSCustomObject]$Settings, + [Parameter(Mandatory)][PSCustomObject]$SelectedLanguage, + [Parameter(Mandatory)][string]$TempFolder + ) + + $files = @(Get-ChildItem "$TempFolder/$($Settings.ManualFolderName)/*.md" -Recurse -Force) + + Write-Host "Start write files:" + + foreach ($file in $files) { + if ($file.Name -ieq 'toc.md') { + continue + } + + $data = Get-Content $file -Encoding UTF8 + + for ($i = 0; $i -lt $data.Length; $i++) { + if ($data[$i].Length -le 0) { + Write-Host $file + + $data[$i] = "> [!WARNING]`r`n> " + $SelectedLanguage.NotTranslatedMessage + "`r`n" + $data | Out-File -Encoding UTF8 $file + + break + } + } + } + + Write-Host "End write files" +} + +function Build-AllLanguagesDoc { + <# + .SYNOPSIS + Builds every enabled translated language. + .OUTPUTS + The exit code of the first language that failed, or 0 when all succeeded. + #> + [CmdletBinding()] + [OutputType([int])] + param ( + [Parameter(Mandatory)] + [PSCustomObject]$Settings, + + [Parameter(Mandatory)] + [PSCustomObject[]]$Language, + + [switch]$KeepTempFolder + ) + + foreach ($lang in Get-TranslatableLanguage -Language $Language) { + $exitCode = Build-NonEnglishDoc -Settings $Settings -SelectedLanguage $lang -KeepTempFolder:$KeepTempFolder + + if ($exitCode -ne 0) { + Write-Error "Failed to build $($lang.Name) documentation. ExitCode: $exitCode" + return $exitCode + } + } + + return 0 +} + +function Start-LocalWebsite { + <# + .SYNOPSIS + Serves the built site locally and opens it in the default browser. + #> + [CmdletBinding()] + param ( + [Parameter(Mandatory)] + [PSCustomObject]$Settings + ) + + Write-Host -ForegroundColor Green "Running local website..." + Write-Host -ForegroundColor Green "Navigate manually to non English website, if you didn't build English documentation." + + Stop-BuildTranscript + + New-Item -ItemType Directory -Verbose -Force -Path $Settings.WebDirectory | Out-Null + + Push-Location $Settings.WebDirectory + try { + Start-Process -FilePath $Settings.LocalTestHostUrl + + docfx serve + } + finally { + Pop-Location + } +} diff --git a/build/Console.ps1 b/build/Console.ps1 new file mode 100644 index 000000000..af91f3499 --- /dev/null +++ b/build/Console.ps1 @@ -0,0 +1,156 @@ +<# +.SYNOPSIS + Console interaction and progress reporting for the documentation build. +.NOTES + This file maps onto a future C# console UI layer (e.g. Spectre.Console prompts). +#> + +function Write-Step { + <# + .SYNOPSIS + Writes a coloured status line followed by a blank line. + #> + [CmdletBinding()] + param ( + [Parameter(Mandatory, Position = 0)] + [AllowEmptyString()] + [string]$Message, + + [System.ConsoleColor]$Color = [System.ConsoleColor]::Green + ) + + Write-Host -ForegroundColor $Color $Message + Write-Host "" +} + +function Confirm-Choice { + <# + .SYNOPSIS + Asks a yes/no question. ENTER means yes. + #> + [CmdletBinding()] + [OutputType([bool])] + param ( + [Parameter(Mandatory, Position = 0)] + [string]$Question + ) + + Write-Host "" + Write-Host -ForegroundColor Cyan $Question + Write-Host "" + Write-Host -ForegroundColor Yellow " [Y] Yes or ENTER" + Write-Host -ForegroundColor Yellow " [N] No" + Write-Host "" + + $answer = Read-Host -Prompt "Your choice [Y, N, or ENTER (default is Y)]" + + return ($answer -ieq "y" -or $answer -eq "") +} + +function Read-BuildOption { + <# + .SYNOPSIS + Shows the main menu and returns the selected option. + .DESCRIPTION + Returns a language code, or one of 'all', 'r' (run local website), 'c' (cancel). + Only languages marked Enabled in languages.json are offered and accepted, so a + disabled code cannot fall through the caller's language lookup. + #> + [CmdletBinding()] + [OutputType([string])] + param ( + [Parameter(Mandatory)] + [PSCustomObject[]]$Language + ) + + $enabled = @(Get-EnabledLanguage -Language $Language) + + Write-Host "" + Write-Host -ForegroundColor Cyan "Please select an option:" + Write-Host "" + foreach ($lang in $enabled) { + Write-Host -ForegroundColor Yellow " [$($lang.Code)] Build $($lang.Name) documentation" + } + Write-Host -ForegroundColor Yellow " [all] Build documentation in all available languages" + Write-Host -ForegroundColor Yellow " [r] Run local website" + Write-Host -ForegroundColor Yellow " [c] Cancel" + Write-Host "" + + $validOptions = @('all', 'r', 'c') + @($enabled | Select-Object -ExpandProperty Code) + + # Bounded so a redirected or exhausted stdin cannot spin forever + for ($attempt = 0; $attempt -lt 10; $attempt++) { + $userChoice = Read-Host -Prompt "Your choice" + + if ($validOptions -contains $userChoice) { + return $userChoice.ToLower() + } + + Write-Host -ForegroundColor Red "'$userChoice' is not a valid option. Expected one of: $($validOptions -join ', ')" + } + + Write-Host -ForegroundColor Red "No valid choice was given. Cancelling." + + return 'c' +} + +function Stop-BuildTranscript { + <# + .SYNOPSIS + Stops the transcript, tolerating the case where none is running. + #> + [CmdletBinding()] + param () + + try { + Stop-Transcript | Out-Null + } + catch { + # No transcript was running; nothing to do. + } +} + +function Wait-ForUserExit { + <# + .SYNOPSIS + Holds the console open so the user can read the output. No-op in CI. + #> + [CmdletBinding()] + param ( + [switch]$Interactive + ) + + if ($Interactive) { + Read-Host -Prompt "Press ENTER to exit..." | Out-Null + } +} + +function Exit-WithError { + <# + .SYNOPSIS + Reports a fatal error, closes the transcript and terminates with a non-zero exit code. + .DESCRIPTION + Uses `exit` rather than `return` so the exit code reaches the .bat wrappers, which end + with `exit $LastExitCode`, and therefore reaches CI. + #> + [CmdletBinding()] + param ( + [Parameter(Mandatory)] + [string]$Message, + + [int]$ExitCode = 1, + + [switch]$Interactive + ) + + Write-Error $Message + + Stop-BuildTranscript + Wait-ForUserExit -Interactive:$Interactive + + if ($ExitCode -eq 0) { + $ExitCode = 1 + } + + exit $ExitCode +} diff --git a/build/DocFx.ps1 b/build/DocFx.ps1 new file mode 100644 index 000000000..d974de14b --- /dev/null +++ b/build/DocFx.ps1 @@ -0,0 +1,89 @@ +<# +.SYNOPSIS + DocFX invocation and API metadata management. +.NOTES + This file maps onto a future C# DocFxRunner. Invoke-DocFx is the single place where the + external tool is called, so a port only has to replace one function body with Process.Start. +#> + +function Invoke-DocFx { + <# + .SYNOPSIS + Runs docfx and returns the exit code of that invocation. + .DESCRIPTION + Callers must capture the return value immediately. Reading $LASTEXITCODE later picks up + whichever native command ran most recently, which is how build failures used to be + misreported. + #> + [CmdletBinding()] + [OutputType([int])] + param ( + [Parameter(Mandatory)] + [string[]]$Argument + ) + + docfx @Argument | Write-Host + + return $LASTEXITCODE +} + +function New-ApiMetadata { + <# + .SYNOPSIS + Generates API metadata from the C# sources. DocFX runs dotnet restore itself. + #> + [CmdletBinding()] + [OutputType([int])] + param ( + [Parameter(Mandatory)] + [PSCustomObject]$Settings + ) + + Write-Step "Generating API documentation..." + + return Invoke-DocFx -Argument @('metadata', $Settings.DocFxConfigFile) +} + +function Test-ApiMetadata { + <# + .SYNOPSIS + Returns $true when previously generated API metadata is available for reuse. + #> + [CmdletBinding()] + [OutputType([bool])] + param ( + [Parameter(Mandatory)] + [PSCustomObject]$Settings + ) + + if (-not (Test-Path $Settings.ApiDirectory)) { + return $false + } + + $ymlFiles = @(Get-ChildItem -Path $Settings.ApiDirectory -Filter '*.yml' -ErrorAction SilentlyContinue) + + return $ymlFiles.Count -gt 0 +} + +function Remove-ApiDocumentation { + <# + .SYNOPSIS + Deletes generated API metadata so the site can be built without API documentation. + #> + [CmdletBinding()] + param ( + [Parameter(Mandatory)] + [PSCustomObject]$Settings + ) + + Write-Host "" + Write-Host -ForegroundColor Green "Erasing API documentation..." + + if (Test-Path $Settings.ApiManifestFile) { + Remove-Item "$($Settings.ApiDirectory)/*.yml" -Recurse -Verbose + Remove-Item $Settings.ApiManifestFile -Verbose + } + else { + Write-Warning "Could not delete APIDoc. The Path $($Settings.ApiManifestFile) does not exist or is not valid." + } +} diff --git a/build/FileUtility.ps1 b/build/FileUtility.ps1 new file mode 100644 index 000000000..b23a6c315 --- /dev/null +++ b/build/FileUtility.ps1 @@ -0,0 +1,103 @@ +<# +.SYNOPSIS + Shared path and file editing helpers. +.NOTES + This file maps onto a future C# PathHelper / FileEditor. Get-RelativePath becomes + Path.GetRelativePath and Update-FileContent becomes a small file-rewriting helper. +#> + +function Get-RelativePath { + <# + .SYNOPSIS + Returns $Path expressed relative to $BasePath, using forward slashes. + #> + [CmdletBinding()] + [OutputType([string])] + param ( + [Parameter(Mandatory)] + [string]$BasePath, + + [Parameter(Mandatory)] + [string]$Path + ) + + $separators = @([System.IO.Path]::DirectorySeparatorChar, [System.IO.Path]::AltDirectorySeparatorChar) + $baseUri = [Uri]((Resolve-Path $BasePath).Path.TrimEnd($separators) + [System.IO.Path]::DirectorySeparatorChar) + $pathUri = [Uri](Resolve-Path $Path).Path + + return [Uri]::UnescapeDataString($baseUri.MakeRelativeUri($pathUri).ToString()) +} + +function Set-Utf8Content { + <# + .SYNOPSIS + Writes lines as UTF-8 without a byte order mark. + .DESCRIPTION + Windows PowerShell's Set-Content defaults to ANSI, which corrupts non-ASCII characters, + and its -Encoding UTF8 emits a BOM. Markdown and YAML written by this build should be + BOM-less UTF-8. + #> + [CmdletBinding()] + param ( + [Parameter(Mandatory)] + [string]$Path, + + [Parameter(Mandatory)] + [AllowEmptyCollection()] + [AllowEmptyString()] + [string[]]$Content + ) + + # WriteAllLines resolves relative paths against the process directory, not the PowerShell + # location, so resolve them here. + $fullPath = if ([System.IO.Path]::IsPathRooted($Path)) { + [System.IO.Path]::GetFullPath($Path) + } + else { + [System.IO.Path]::GetFullPath((Join-Path (Get-Location).ProviderPath $Path)) + } + + [System.IO.File]::WriteAllLines($fullPath, $Content, [System.Text.UTF8Encoding]::new($false)) +} + +function Update-FileContent { + <# + .SYNOPSIS + Applies a set of ordered replacements to a UTF-8 text file. + .DESCRIPTION + Replacements are literal by default. Pass -AsRegex when the keys are genuine patterns + with capture groups; note that PowerShell's -replace is case-insensitive, whereas the + literal path is ordinal. + .PARAMETER Replacement + An ordered dictionary of search string to replacement string. Order matters: each entry + is applied to the result of the previous one. + #> + [CmdletBinding()] + param ( + [Parameter(Mandatory)] + [string]$Path, + + [Parameter(Mandatory)] + [System.Collections.IDictionary]$Replacement, + + [switch]$AsRegex + ) + + if (-not (Test-Path -LiteralPath $Path)) { + Write-Warning "Cannot update '$Path'. The file does not exist." + return + } + + $content = @(Get-Content -LiteralPath $Path -Encoding UTF8) + + foreach ($entry in $Replacement.GetEnumerator()) { + if ($AsRegex) { + $content = $content -replace $entry.Key, $entry.Value + } + else { + $content = @($content | ForEach-Object { $_.Replace($entry.Key, $entry.Value) }) + } + } + + $content | Set-Content -LiteralPath $Path -Encoding UTF8 +} diff --git a/build/Languages.ps1 b/build/Languages.ps1 new file mode 100644 index 000000000..598b98201 --- /dev/null +++ b/build/Languages.ps1 @@ -0,0 +1,72 @@ +<# +.SYNOPSIS + Reads and filters the language configuration. +.NOTES + This file maps onto a future C# Language record plus the service that loads languages.json. +#> + +function Read-LanguageConfiguration { + <# + .SYNOPSIS + Loads languages.json. + #> + [CmdletBinding()] + [OutputType([PSCustomObject[]])] + param ( + [Parameter(Mandatory)] + [PSCustomObject]$Settings + ) + + return @(Get-Content $Settings.LanguageJsonPath -Encoding UTF8 | ConvertFrom-Json) +} + +function Get-EnabledLanguage { + <# + .SYNOPSIS + All languages that are switched on, primary language first. + #> + [CmdletBinding()] + [OutputType([PSCustomObject[]])] + param ( + [Parameter(Mandatory)] + [PSCustomObject[]]$Language + ) + + $enabled = @($Language | Where-Object { $_.Enabled }) + + # Ordered explicitly rather than sorted, so the menu always lists English first. + return @($enabled | Where-Object { $_.IsPrimary }) + @($enabled | Where-Object { -not $_.IsPrimary }) +} + +function Get-TranslatableLanguage { + <# + .SYNOPSIS + The enabled non-primary languages, i.e. those built from a _tmp overlay folder. + #> + [CmdletBinding()] + [OutputType([PSCustomObject[]])] + param ( + [Parameter(Mandatory)] + [PSCustomObject[]]$Language + ) + + return @($Language | Where-Object { $_.Enabled -and -not $_.IsPrimary }) +} + +function Find-TranslatableLanguage { + <# + .SYNOPSIS + Resolves a language code to its configuration, or $null if it is not translatable. + #> + [CmdletBinding()] + [OutputType([PSCustomObject])] + param ( + [Parameter(Mandatory)] + [PSCustomObject[]]$Language, + + [Parameter(Mandatory)] + [string]$Code + ) + + return Get-TranslatableLanguage -Language $Language | Where-Object { $_.Code -eq $Code } | Select-Object -First 1 +} diff --git a/build/PostProcessing.ps1 b/build/PostProcessing.ps1 new file mode 100644 index 000000000..e73e73674 --- /dev/null +++ b/build/PostProcessing.ps1 @@ -0,0 +1,173 @@ +<# +.SYNOPSIS + Fixes up the generated site after DocFX has run, and copies deployment extras into _site. +.NOTES + This file maps onto a future C# SitePostProcessor. +#> + +function Update-DocFxDocUrl { + <# + .SYNOPSIS + Corrects the GitHub "edit this page" links in a translated site. + .DESCRIPTION + Non-English sites are built from a temporary _tmp folder, so DocFX derives its + docfx:docurl meta tag and anchor hrefs from a path that does not exist on GitHub. Pages + that really are translated are pointed at the language folder; everything else falls + back to the English source. + .PARAMETER SelectedLanguage + The language whose output should be corrected. + .NOTES + Progress is displayed interactively and suppressed in non-interactive sessions such as + CI/CD pipelines. + #> + [CmdletBinding()] + param ( + [Parameter(Mandatory)] + [PSCustomObject]$Settings, + + [Parameter(Mandatory)] + [PSCustomObject]$SelectedLanguage + ) + + $code = $SelectedLanguage.Code + $sourcePath = (Resolve-Path (Get-LanguageSourcePath -Settings $Settings -Code $code)).Path + $outputPath = Get-LanguageOutputPath -Settings $Settings -Code $code + $tempFolder = Get-LanguageTempPath -Settings $Settings -Code $code + + # Relative paths of the files that have actually been translated, e.g. "manual/index.md" + $translatedPaths = @( + Get-ChildItem "$sourcePath/*.md" -Recurse -Force | + ForEach-Object { Get-RelativePath -BasePath $sourcePath -Path $_.FullName } + ) + + $htmlFiles = @(Get-ChildItem "$outputPath/*.html" -Recurse) + + Write-Host -ForegroundColor Yellow "Post-processing docfx:docurl in $($htmlFiles.Count) files..." + + # Matches the temporary build folder inside the docurl meta tag and inside anchor hrefs + $docUrlPattern = '()' + $anchorPattern = '()' + + for ($i = 0; $i -lt $htmlFiles.Count; $i++) { + $htmlFile = $htmlFiles[$i] + + $relativePath = Get-RelativePath -BasePath $outputPath -Path $htmlFile.FullName + $relativeMarkdownPath = [System.IO.Path]::ChangeExtension($relativePath, '.md') + + # Translated pages link to the language folder, everything else to the English source + $targetFolder = if ($translatedPaths -contains $relativeMarkdownPath) { $code } else { $Settings.PrimaryLanguageCode } + + Update-FileContent -Path $htmlFile.FullName -AsRegex -Replacement ([ordered]@{ + $docUrlPattern = "`${1}/$targetFolder/`${3}" + $anchorPattern = "`${1}/$targetFolder/`${3}" + }) + + # Check if the script is running in an interactive session before writing progress. + # We don't want to write progress when running in a build pipeline. + if ($host.UI.RawUI) { + Write-Progress -Activity "Processing files" -Status "$($i + 1) of $($htmlFiles.Count) processed" -PercentComplete ((($i + 1) / $htmlFiles.Count) * 100) + } + } + + Write-Host "" + Write-Step "Post-processing completed." +} + +function Update-Sitemap { + <# + .SYNOPSIS + Rewrites sitemap.xml so every URL points at the /latest/ alias. + #> + [CmdletBinding()] + param ( + [Parameter(Mandatory)] + [PSCustomObject]$Settings + ) + + Write-Step "Post-processing $($Settings.SitemapFileName), adding $($Settings.LatestAlias)/$($Settings.PrimaryLanguageCode) to url" -Color Yellow + + $primaryOutput = Get-LanguageOutputPath -Settings $Settings -Code $Settings.PrimaryLanguageCode + $sitemapFile = "$primaryOutput/$($Settings.SitemapFileName)" + + Update-FileContent -Path $sitemapFile -Replacement ([ordered]@{ + $Settings.DocsUrl = "$($Settings.DocsUrl)/$($Settings.LatestAlias)/$($Settings.PrimaryLanguageCode)" + }) + + Write-Step "Post-processing $($Settings.SitemapFileName) completed." +} + +function Update-NotFoundPage { + <# + .SYNOPSIS + Rewrites the relative asset references in 404.html to absolute, version-qualified paths. + .DESCRIPTION + 404.html is served from arbitrary URLs, so its relative references to CSS, JS and images + would otherwise resolve against the requested path rather than the site root. + #> + [CmdletBinding()] + param ( + [Parameter(Mandatory)] + [PSCustomObject]$Settings + ) + + Write-Step "Post-processing $($Settings.NotFoundFileName), adding version/$($Settings.PrimaryLanguageCode) to url" -Color Yellow + + $primaryOutput = Get-LanguageOutputPath -Settings $Settings -Code $Settings.PrimaryLanguageCode + $notFoundFile = "$primaryOutput/$($Settings.NotFoundFileName)" + $prefix = "/$($Settings.Version)/$($Settings.PrimaryLanguageCode)/" + + $replacements = [ordered]@{} + + foreach ($asset in $Settings.NotFoundAssetPath) { + $replacements[$asset] = "$prefix$asset" + } + + # DocFX emits script tags with a leading ./ + foreach ($script in $Settings.NotFoundScriptPath) { + $replacements["./$script"] = "$prefix$script" + } + + Update-FileContent -Path $notFoundFile -Replacement $replacements + + Write-Step "Post-processing $($Settings.NotFoundFileName) completed." +} + +function Copy-ExtraItem { + <# + .SYNOPSIS + Copies deployment files that DocFX does not produce into the web root. + #> + [CmdletBinding()] + param ( + [Parameter(Mandatory)] + [PSCustomObject]$Settings + ) + + $webDirectory = $Settings.WebDirectory + + foreach ($file in @($Settings.VersionsFile, $Settings.WebConfigFile, $Settings.RobotsFile)) { + Write-Step "Copying $file into $webDirectory/" -Color Yellow + Copy-Item $file "$webDirectory/" + } + + Write-Step "Updating $($Settings.WebConfigFile)" -Color Yellow + + Update-FileContent -Path "$webDirectory/$($Settings.WebConfigFile)" -Replacement ([ordered]@{ + $Settings.DeploymentVersionToken = $Settings.Version + }) + + Write-Step "Updating $($Settings.WebConfigFile) completed." + + # This is needed for Stride Launcher, which loads Release Notes + $primaryOutput = Get-LanguageOutputPath -Settings $Settings -Code $Settings.PrimaryLanguageCode + $releaseNotesDirectory = "$primaryOutput/$($Settings.ReleaseNotesFolderName)" + + Write-Step "Copying $($Settings.ReleaseNotesFile) into $releaseNotesDirectory/" -Color Yellow + + # Without an existing directory Copy-Item would silently create a file named ReleaseNotes + if (-not (Test-Path $releaseNotesDirectory)) { + New-Item -ItemType Directory -Path $releaseNotesDirectory -Force | Out-Null + } + + Copy-Item $Settings.ReleaseNotesFile "$releaseNotesDirectory/" +} diff --git a/build/Settings.ps1 b/build/Settings.ps1 new file mode 100644 index 000000000..68b6bbc77 --- /dev/null +++ b/build/Settings.ps1 @@ -0,0 +1,133 @@ +<# +.SYNOPSIS + Build settings for the Stride documentation build. +.DESCRIPTION + Every path, file name, URL and token used by BuildDocs.ps1 is defined here exactly once. + Only a handful of root values are literals; everything else is composed from them, so + changing the primary language folder or the output folder is a one-line edit. +.NOTES + This file maps onto a future C# BuildSettings options class. The derivation below is what + that class would do in its constructor. +#> + +function New-BuildSettings { + [CmdletBinding()] + [OutputType([PSCustomObject])] + param ( + [Parameter(Mandatory)] + [string]$Version, + + [Parameter(Mandatory)] + [string]$RootPath + ) + + # Roots. Everything below is derived from these four values. + $primary = 'en' + $web = '_site' + $site = "$web/$Version" + $architecture = "$primary/contributors/engine/architecture" + + [PSCustomObject]@{ + RootPath = $RootPath + Version = $Version + PrimaryLanguageCode = $primary + + # Directories + SourceDirectory = $primary + WebDirectory = $web + SiteDirectory = $site + TempDirectorySuffix = '_tmp' + ApiDirectory = "$primary/api" + ArchitectureDirectory = $architecture + EngineDocsDirectory = '../stride/docs' + ManualFolderName = 'manual' + ReleaseNotesFolderName = 'ReleaseNotes' + + # Files + DocFxConfigFile = "$primary/docfx.json" + LanguageJsonPath = "$primary/languages.json" + ApiManifestFile = "$primary/api/.manifest" + ArchitectureIndexFile = "$primary/contributors/engine/architecture-index.md" + ReleaseNotesFile = "$primary/ReleaseNotes/ReleaseNotes.md" + VersionsFile = 'versions.json' + WebConfigFile = 'web.config' + RobotsFile = 'robots.txt' + LogPath = './build.log' + + # File names + IndexFileName = 'index.md' + TocFileName = 'toc.yml' + ReadmeFileName = 'README.md' + SitemapFileName = 'sitemap.xml' + NotFoundFileName = '404.html' + + # URLs and tokens + DocsUrl = 'https://doc.stride3d.net' + LatestAlias = 'latest' + EngineRepositoryUrl = 'https://github.com/stride3d/stride/tree/master' + LocalTestHostUrl = "http://localhost:8080/$Version/$primary/index.html" + DeploymentVersionToken = '%deployment_version%' + + # Relative asset paths rewritten to absolute ones in 404.html. Assets are referenced + # bare (href="favicon.ico"); scripts are referenced with a leading ./ by DocFX. + NotFoundAssetPath = @( + 'favicon.ico' + 'public/docfx.min.css' + 'public/main.css' + 'toc.html' + 'media/stride-logo-red.svg' + ) + NotFoundScriptPath = @( + 'public/main.js' + 'public/docfx.min.js' + ) + } +} + +function Get-LanguageSourcePath { + <# + .SYNOPSIS + The checked-in source folder for a language, e.g. "jp". + #> + [CmdletBinding()] + [OutputType([string])] + param ( + [Parameter(Mandatory)][PSCustomObject]$Settings, + [Parameter(Mandatory)][string]$Code + ) + + return $Code +} + +function Get-LanguageTempPath { + <# + .SYNOPSIS + The scratch folder a language is built from, e.g. "jp_tmp". + .DESCRIPTION + English content is copied here first, then overlaid with the translated files, so that + untranslated pages fall back to English. + #> + [CmdletBinding()] + [OutputType([string])] + param ( + [Parameter(Mandatory)][PSCustomObject]$Settings, + [Parameter(Mandatory)][string]$Code + ) + + return "$Code$($Settings.TempDirectorySuffix)" +} + +function Get-LanguageOutputPath { + <# + .SYNOPSIS + The built output folder for a language, e.g. "_site/4.4/jp". + #> + [CmdletBinding()] + [OutputType([string])] + param ( + [Parameter(Mandatory)][PSCustomObject]$Settings, + [Parameter(Mandatory)][string]$Code + ) + + return "$($Settings.SiteDirectory)/$Code" +} diff --git a/en/contributors/documentation/documentation-generation-pipeline.md b/en/contributors/documentation/documentation-generation-pipeline.md index 3cf6e98cc..4d4ec5310 100644 --- a/en/contributors/documentation/documentation-generation-pipeline.md +++ b/en/contributors/documentation/documentation-generation-pipeline.md @@ -33,6 +33,23 @@ This section outlines the file processing carried out by Docfx during the docume - **Warnings (API Metadata):** 200 instances of missing or incorrect references - **API Files:** 2825 files processed, resulting in 2133 HTML files +## Script Structure + +`BuildDocs.ps1` is the entry point and holds the parameters, the transcript and the overall +orchestration. The individual build steps live in the `build` folder and are dot-sourced by the +entry point: + +| File | Responsibility | +| --- | --- | +| `build/Settings.ps1` | All paths, file names, URLs and tokens, derived from a few root values | +| `build/Languages.ps1` | Reads and filters `languages.json` | +| `build/Console.ps1` | Menu, prompts, progress messages and error exits | +| `build/FileUtility.ps1` | Relative paths and file rewriting helpers | +| `build/DocFx.ps1` | Every `docfx` invocation, plus API metadata management | +| `build/ArchitectureDocs.ps1` | Imports the engine architecture docs and generates their `toc.yml` | +| `build/PostProcessing.ps1` | Fixes up the generated site and copies deployment extras | +| `build/Build.ps1` | The per-language build steps | + ## Docs Build Workflow In this part, we elaborate on the individual steps involved in the documentation build workflow for the Stride Docs project. @@ -41,37 +58,40 @@ In this part, we elaborate on the individual steps involved in the documentation - Initiates the workflow by reading the `$BuildAll` parameter. - If set to 'Yes', it proceeds to generate all languages and the Stride API automatically, which is particularly useful for CI/CD. - If set to 'No', it will prompt the user to select languages through an interactive command-line UI. - - Sets the `$Version` parameter based on the `-Version` command-line argument or fetches it from `version.json` if the argument is not provided. -- **Read-LanguageConfigurations** + - Sets the `$Version` parameter based on the `-Version` command-line argument or fetches the highest entry from `versions.json` if the argument is not provided. +- **Read-LanguageConfiguration** - Reads `languages.json` to identify which languages should be generated. - **BuildAll** - - Pre-configures some variables for non-interactive mode, effectively skipping the `Get-UserInput` step. -- **Get-UserInput** - - In interactive mode, this step prompts the user to choose the languages to generate, as well as whether to launch a local web server. -- **Ask-IncludeAPI** - - Further queries if the user wants the Stride API included in the documentation build. -- **Ask-UseExistingAPI** - - Queries if the user wants to re-use already generated Stride API yml files. + - Pre-configures some variables for non-interactive mode, effectively skipping the `Read-BuildOption` step. +- **Read-BuildOption** + - In interactive mode, this step prompts the user to choose the languages to generate, as well as whether to launch a local web server. Only languages marked `Enabled` in `languages.json` are offered and accepted. +- **Confirm-Choice** + - A single yes/no prompt used for all interactive questions: whether to include the Stride API, whether to re-use already generated Stride API yml files, and whether to refresh the engine architecture docs. - **Start-LocalWebsite** - If selected, launches a local web server to host the generated website. -- **Generate-APIDoc** +- **New-ApiMetadata** - Executes `docfx.exe` to generate the metadata needed for the Stride API documentation. -- **Remove-APIDoc** +- **Remove-ApiDocumentation** - Removes the generated API metadata. +- **Engine Architecture Docs** + - Copy-ArchitectureDocs + - Mirrors the `docs` folder of the sibling [stride](https://github.com/stride3d/stride) repository into `en/contributors/engine/architecture`, rewriting links and renaming `README.md` files to `index.md`. The step is skipped with a warning when the engine repository is not checked out next to `stride-docs`. + - New-ArchitectureDocsToc + - Generates `toc.yml` for the imported folder, taking each entry's title from the document's first heading. - **Build-EnglishDoc** - - Uses `docfx.exe` to build the English documentation, incorporating the Stride API documentation if metadata is available. + - Uses `docfx.exe` to build the English documentation, incorporating the Stride API documentation if metadata is available, and then builds the PDF unless `-SkipPdfBuilding` was passed. - **PostProcessing Steps** - - PostProcessing-FixingSitemap + - Update-Sitemap - Adjusts the `sitemap.xml` to use '/latest/en' paths, allowing the most current version to maintain a consistent URL. - - PostProcessing-Fixing404AbsolutePath + - Update-NotFoundPage - Modifies asset (CSS, JS, ) paths in `404.html` to be absolute, as required by IIS for 404 page. - - Copy-ExtraItems + - Copy-ExtraItem - Copies additional items like `versions.json`, `web.config`, `ReleaseNotes.md` and `robots.txt`, while also updating the `%deployment_version%` parameter in the `web.config` file. -- **Build-AllLanguagesDocs** - - Iterates over all selected languages and triggers the `Build-NonEnglishDoc` function for each. +- **Build-AllLanguagesDoc** + - Iterates over all enabled non-English languages and triggers the `Build-NonEnglishDoc` function for each. If any language fails, the build stops and reports that language's exit code. - **Build-NonEnglishDoc** - Executes `docfx.exe` to compile non-English documentation, incorporating Stride API documentation if metadata is present. -- **PostProcessing-DocFxDocUrl** +- **Update-DocFxDocUrl** - Adjusts HTML tags and GitHub links, removing any `_tmp` suffixes. Also updates GitHub links to English if the translation is unavailable. ## Workflow Diagram @@ -85,25 +105,28 @@ graph TB %% Nodes Start[Start] - A[Read-LanguageConfigurations] + A[Read-LanguageConfiguration] B{BuildAll} - C[Get-UserInput] - D[Generate-APIDoc] - E{Ask-IncludeAPI} - E1{Ask-UseExistingAPI} + C[Read-BuildOption] + D[New-ApiMetadata] + E{Confirm-Choice: include API} + E1{Confirm-Choice: reuse API metadata} + E2{Confirm-Choice: architecture docs} End[End] F[Start-LocalWebsite] G[Cancel] - H[Remove-APIDoc] - M{isEnLanguage or isAllLanguages} + H[Remove-ApiDocumentation] + I[Copy-ArchitectureDocs] + I1[New-ArchitectureDocsToc] + M{isPrimaryLanguage or isAllLanguages} N[Build-EnglishDoc] - O[PostProcessing-FixingSitemap] - O1[PostProcessing-Fixing404AbsolutePath] - P[Copy-ExtraItems] + O[Update-Sitemap] + O1[Update-NotFoundPage] + P[Copy-ExtraItem] R{isAllLanguages} - S[Build-AllLanguagesDocs] + S[Build-AllLanguagesDoc] T[Build-NonEnglishDoc] - Y[PostProcessing-DocFxDocUrl] + Y[Update-DocFxDocUrl] Z[End] %% Edges @@ -120,20 +143,21 @@ graph TB F1 --> End G --> End E1 -->|No| D - E1 -->|Yes| M + E1 -->|Yes| E2 + H --> E2 subgraph Documentation Generation - H --> M - D --> D1{{docfx metadata}} --> M + D --> D1{{docfx metadata}} --> E2 + E2 -->|Yes| I --> I1 --> M + E2 -->|No| M M -->|Yes| N M -->|No| R - N --> DocFX{{docfx build}} --> O --> O1--> P + N --> DocFX{{docfx build}} --> X1{{docfx pdf}} --> O --> O1--> P P --> R R -->|Yes| S R -->|No| T S --> T T --> X{{docfx build}} - X --> X1{{docfx pdf}} - X1 --> Y + X --> Y Y --> Z end ``` \ No newline at end of file