BuildDocs.ps1 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532
  1. <#
  2. .SYNOPSIS
  3. This script builds documentation (manuals, tutorials, release notes) in selected language(s) from the languages.json file and optionally includes API documentation.
  4. .DESCRIPTION
  5. 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.
  6. .NOTES
  7. 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.
  8. .LINK
  9. https://github.com/stride3d/stride-docs
  10. .LINK
  11. https://github.com/stride3d/stride-docs/blob/master/en/languages.json
  12. .LINK
  13. https://dotnet.github.io/docfx/index.html
  14. .PARAMETER BuildAll
  15. Switch parameter. If provided, the script will build documentation in all available languages and include API documentation.
  16. .PARAMETER Version
  17. The Version to build the Docs, the default is the latest version
  18. .EXAMPLE
  19. .\BuildDocs.ps1 -BuildAll
  20. In this example, the script will build the documentation in all available languages and include API documentation. Use this in GitHub Actions.
  21. .EXAMPLE
  22. .\BuildDocs.ps1
  23. In this example, the script will prompt the user to select an operation and an optional language. If the user chooses to build the documentation, the script will also ask if they want to include API documentation.
  24. #>
  25. param (
  26. [switch]$BuildAll,
  27. [ArgumentCompleter({
  28. [OutputType([System.Management.Automation.CompletionResult])]
  29. param([string] $CommandName,[string] $ParameterName,[string] $WordToComplete,[System.Management.Automation.Language.CommandAst] $CommandAst,[System.Collections.IDictionary] $FakeBoundParameters)
  30. return (Get-Content $PSScriptRoot\versions.json -Encoding UTF8 | ConvertFrom-Json).versions
  31. })]
  32. $Version = $((Get-Content $PSScriptRoot\versions.json -Encoding UTF8 | ConvertFrom-Json).versions | Sort-Object -Descending | Select-Object -First 1)
  33. )
  34. $Settings = [PSCustomObject]@{
  35. Version = $Version
  36. SiteDirectory = "_site/$Version"
  37. LocalTestHostUrl = "http://localhost:8080/$Version/en/index.html"
  38. LanguageJsonPath = "en\languages.json"
  39. LogPath = ".\build.log"
  40. TempDirectory = "_tmp"
  41. WebDirectory = "_site"
  42. IndexFileName = "index.md"
  43. ManualFolderName = "manual"
  44. DocsUrl = "https://doc.stride3d.net"
  45. }
  46. # To Do fix, GitHub references, fix sitemap links to latest/en/
  47. function Read-LanguageConfigurations {
  48. return Get-Content $Settings.LanguageJsonPath -Encoding UTF8 | ConvertFrom-Json
  49. }
  50. function Get-UserInput {
  51. Write-Host ""
  52. Write-Host -ForegroundColor Cyan "Please select an option:"
  53. Write-Host ""
  54. Write-Host -ForegroundColor Yellow " [en] Build English documentation"
  55. foreach ($lang in $languages) {
  56. if ($lang.Enabled -and -not $lang.IsPrimary) {
  57. Write-Host -ForegroundColor Yellow " [$($lang.Code)] Build $($lang.Name) documentation"
  58. }
  59. }
  60. Write-Host -ForegroundColor Yellow " [all] Build documentation in all available languages"
  61. Write-Host -ForegroundColor Yellow " [r] Run local website"
  62. Write-Host -ForegroundColor Yellow " [c] Cancel"
  63. Write-Host ""
  64. $validOptions = @('all', 'r', 'c') + $($languages | Select-Object -ExpandProperty Code)
  65. while($true)
  66. {
  67. $userChoice = Read-Host -Prompt "Your choice"
  68. if($validOptions -contains $userChoice)
  69. {
  70. return $userChoice.ToLower()
  71. }
  72. }
  73. Write-Error "No valid Choice was given."
  74. }
  75. function Ask-IncludeAPI {
  76. Write-Host ""
  77. Write-Host -ForegroundColor Cyan "Do you want to include API?"
  78. Write-Host ""
  79. Write-Host -ForegroundColor Yellow " [Y] Yes or ENTER"
  80. Write-Host -ForegroundColor Yellow " [N] No"
  81. Write-Host ""
  82. $answer = Read-Host -Prompt "Your choice [Y, N, or ENTER (default is Y)]"
  83. return ($answer -ieq "y" -or $answer -eq "")
  84. }
  85. function Ask-UseExistingAPI {
  86. Write-Host ""
  87. Write-Host -ForegroundColor Cyan "Do you want to use already generated API metadata?"
  88. Write-Host ""
  89. Write-Host -ForegroundColor Yellow " [Y] Yes or ENTER"
  90. Write-Host -ForegroundColor Yellow " [N] No"
  91. Write-Host ""
  92. $answer = Read-Host -Prompt "Your choice [Y, N, or ENTER (default is Y)]"
  93. return ($answer -ieq "y" -or $answer -eq "")
  94. }
  95. function Copy-ExtraItems {
  96. Write-Host -ForegroundColor Yellow "Copying versions.json into $($Settings.WebDirectory)/"
  97. Write-Host ""
  98. Copy-Item versions.json "$($Settings.WebDirectory)/"
  99. Write-Host -ForegroundColor Yellow "Copying web.config into $($Settings.WebDirectory)/"
  100. Write-Host ""
  101. Copy-Item web.config "$($Settings.WebDirectory)/"
  102. Write-Host -ForegroundColor Yellow "Updating web.config"
  103. Write-Host ""
  104. $webConfig = "$($Settings.WebDirectory)/web.config"
  105. $content = Get-Content $webConfig -Encoding UTF8
  106. $content = $content -replace "%deployment_version%", $Settings.Version
  107. $content | Set-Content -Encoding UTF8 $webConfig
  108. Write-Host -ForegroundColor Green "Updating web.config completed."
  109. Write-Host ""
  110. # This is needed for Stride Launcher, which loads Release Notes
  111. Write-Host -ForegroundColor Yellow "Copying ReleaseNotes.md into $($Settings.SiteDirectory)/en/ReleaseNotes/"
  112. Write-Host ""
  113. Copy-Item en/ReleaseNotes/ReleaseNotes.md "$($Settings.SiteDirectory)/en/ReleaseNotes/"
  114. Write-Host -ForegroundColor Yellow "Copying robots.txt into $($Settings.WebDirectory)/"
  115. Write-Host ""
  116. Copy-Item robots.txt "$($Settings.WebDirectory)/"
  117. }
  118. function Start-LocalWebsite {
  119. Write-Host -ForegroundColor Green "Running local website..."
  120. Write-Host -ForegroundColor Green "Navigate manually to non English website, if you didn't build English documentation."
  121. Stop-Transcript
  122. New-Item -ItemType Directory -Verbose -Force -Path $Settings.WebDirectory | Out-Null
  123. Set-Location $Settings.WebDirectory
  124. Start-Process -FilePath $Settings.LocalTestHostUrl
  125. docfx serve
  126. Set-Location ..
  127. }
  128. function Generate-APIDoc {
  129. Write-Host -ForegroundColor Green "Generating API documentation..."
  130. Write-Host ""
  131. # Build metadata from C# source, docfx runs dotnet restore
  132. docfx metadata en/docfx.json | Write-Host
  133. return $LastExitCode
  134. }
  135. function Remove-APIDoc {
  136. Write-Host ""
  137. Write-Host -ForegroundColor Green "Erasing API documentation..."
  138. $APIDocPath = "en/api/.manifest"
  139. if (Test-Path $APIDocPath) {
  140. Remove-Item en/api/*yml -recurse -Verbose
  141. Remove-Item $APIDocPath -Verbose
  142. } else{
  143. Write-Warning "Could not delete APIDoc. The Path $APIDocPath does not exist or is not valid."
  144. }
  145. }
  146. function Build-EnglishDoc {
  147. $outputDirectory = "$($Settings.SiteDirectory)/en"
  148. Write-Host -ForegroundColor Yellow "Start building English documentation. Output: $($outputDirectory)"
  149. Write-Host ""
  150. # Output to both build.log and console
  151. docfx build en/docfx.json -o $outputDirectory | Write-Host
  152. return $LastExitCode
  153. }
  154. function Build-NonEnglishDoc {
  155. param (
  156. $SelectedLanguage
  157. )
  158. if ($SelectedLanguage -and $SelectedLanguage.Code -ne 'en') {
  159. Write-Host "-------------------------------------------------------------------------------"
  160. Write-Host ""
  161. Write-Host -ForegroundColor Yellow "Start building $($SelectedLanguage.Name) documentation."
  162. Write-Host ""
  163. $langFolder = "$($SelectedLanguage.Code)$($Settings.TempDirectory)"
  164. if (Test-Path $langFolder) {
  165. Remove-Item $langFolder/* -recurse -Verbose
  166. }
  167. else {
  168. $discard = New-Item -Path $langFolder -ItemType Directory -Verbose
  169. }
  170. # Copy all files from en folder to the selected language folder, this way we can keep en files that are not translated
  171. Copy-Item en/* -Recurse $langFolder -Force
  172. # Get all translated files from the selected language folder
  173. $files = Get-ChildItem "$langFolder/$($Settings.ManualFolderName)/*.md" -Recurse -Force
  174. Write-Host "Start write files:"
  175. # Mark files as not translated if they are not in the toc.md file
  176. foreach ($file in $files)
  177. {
  178. if($file.ToString().Contains("toc.md")) {
  179. continue;
  180. }
  181. $data = Get-Content $file -Encoding UTF8
  182. for ($i = 0; $i -lt $data.Length; $i++)
  183. {
  184. $line = $data[$i];
  185. if ($line.length -le 0)
  186. {
  187. Write-Host $file
  188. $data[$i]="> [!WARNING]`r`n> " + $SelectedLanguage.NotTranslatedMessage + "`r`n"
  189. $data | Out-File -Encoding UTF8 $file
  190. break
  191. }
  192. }
  193. }
  194. Write-Host "End write files"
  195. $indexFile = $Settings.IndexFileName
  196. # overwrite en manual page with translated manual page
  197. if (Test-Path ($SelectedLanguage.Code + "/" + $indexFile)) {
  198. Copy-Item ($SelectedLanguage.Code + "/" + $indexFile) $langFolder -Force
  199. }
  200. else {
  201. Write-Warning "$($SelectedLanguage.Code)/"+ $indexFile +" not found. English version will be used."
  202. }
  203. # overwrite en manual pages with translated manual pages
  204. if (Test-Path ($SelectedLanguage.Code + "/" + $Settings.ManualFolderName)) {
  205. Copy-Item ($SelectedLanguage.Code + "/" + $Settings.ManualFolderName) -Recurse -Destination $langFolder -Force
  206. }
  207. else {
  208. Write-Warning "$($SelectedLanguage.Code)/$($Settings.ManualFolderName) not found."
  209. }
  210. # 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
  211. Copy-Item en/docfx.json $langFolder -Force
  212. $SiteDir = $Settings.SiteDirectory
  213. # we replace the en folder with the selected language folder in the docfx.json file
  214. (Get-Content $langFolder/docfx.json) -replace "$SiteDir/en","$SiteDir/$($SelectedLanguage.Code)" | Set-Content -Encoding UTF8 $langFolder/docfx.json
  215. $outputDirectory = "$($Settings.SiteDirectory)/$($SelectedLanguage.Code)"
  216. docfx build $langFolder/docfx.json -o $outputDirectory | Write-Host
  217. if (!$BuildAll) {
  218. Remove-Item $langFolder -Recurse -Verbose
  219. }
  220. PostProcessing-DocFxDocUrl -SelectedLanguage $SelectedLanguage
  221. Write-Host -ForegroundColor Green "$($SelectedLanguage.Name) documentation built."
  222. return $LastExitCode
  223. }
  224. }
  225. function Build-AllLanguagesDocs {
  226. param (
  227. [array]$Languages
  228. )
  229. foreach ($lang in $Languages) {
  230. if ($lang.Enabled -and -not $lang.IsPrimary) {
  231. Build-NonEnglishDoc -SelectedLanguage $lang
  232. if ($exitCode -ne 0)
  233. {
  234. Write-Error "Failed to build $($SelectedLanguage.Name) documentation. ExitCode: $exitCode"
  235. Stop-Transcript
  236. return $exitCode
  237. }
  238. }
  239. }
  240. }
  241. function PostProcessing-DocFxDocUrl {
  242. <#
  243. .SYNOPSIS
  244. 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.
  245. .DESCRIPTION
  246. 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.
  247. .PARAMETER SelectedLanguage
  248. The Language to find the relevant HTML and markdown Files
  249. .NOTES
  250. 1. This function assumes that the folder structure and naming conventions meet the specified conditions.
  251. 2. Progress is displayed interactively, and is suppressed in non-interactive sessions such as CI/CD pipelines.
  252. #>
  253. param (
  254. $SelectedLanguage
  255. )
  256. $translatedFiles = Get-ChildItem "$($SelectedLanguage.Code)/*.md" -Recurse -Force
  257. # Get a list of all HTML files in the _site/<language> directory
  258. $htmlFiles = Get-ChildItem "$($Settings.SiteDirectory)/$($SelectedLanguage.Code)/*.html" -Recurse
  259. # Get the relative paths of the translated files
  260. $relativeTranslatedFilesPaths = $translatedFiles | ForEach-Object { $_.FullName.Replace((Resolve-Path $SelectedLanguage.Code).Path + '\', '') }
  261. Write-Host -ForegroundColor Yellow "Post-processing docfx:docurl in $($htmlFiles.Count) files..."
  262. for ($i = 0; $i -lt $htmlFiles.Count; $i++) {
  263. $htmlFile = $htmlFiles[$i]
  264. # Get the relative path of the HTML file
  265. $relativeHtmlPath = $htmlFile.FullName.Replace((Resolve-Path "$($Settings.SiteDirectory)/$($SelectedLanguage.Code)").Path + '\', '').Replace('.html', '.md')
  266. # Read the content of the HTML file
  267. $content = Get-Content $htmlFile -Encoding UTF8
  268. # Define a regex pattern to match the meta tag with name="docfx:docurl"
  269. $pattern = '(<meta name="docfx:docurl" content=".*?)(/' + $SelectedLanguage.Code + $Settings.TempDirectory+ '/)(.*?">)'
  270. # Define a regex pattern to match the href attribute in the <a> tags
  271. $pattern2 = '(<a href=".*?)(/' + $SelectedLanguage.Code + $Settings.TempDirectory + '/)(.*?">)'
  272. # Check if the HTML file is from the $translatedFiles collection, if so, we will update the path to the
  273. # existing file in GitHub
  274. if ($relativeTranslatedFilesPaths -contains $relativeHtmlPath) {
  275. # Replace /<language>_tmp/ with /<language>/ in the content
  276. $content = $content -replace $pattern, "`${1}/$($SelectedLanguage.Code)/`${3}"
  277. $content = $content -replace $pattern2, "`${1}/$($SelectedLanguage.Code)/`${3}"
  278. } else {
  279. # Replace /<language>_tmp/ with /en/ in the content
  280. $content = $content -replace $pattern, '${1}/en/${3}'
  281. $content = $content -replace $pattern2, '${1}/en/${3}'
  282. }
  283. # Write the updated content back to the HTML file
  284. $content | Set-Content -Encoding UTF8 $htmlFile
  285. # Check if the script is running in an interactive session before writing progress
  286. # We don't want to write progress when running in a non-interactive session, such as in a build pipeline
  287. if ($host.UI.RawUI) {
  288. Write-Progress -Activity "Processing files" -Status "$i of $($htmlFiles.Count) processed" -PercentComplete (($i / $htmlFiles.Count) * 100)
  289. }
  290. }
  291. Write-Host ""
  292. Write-Host -ForegroundColor Green "Post-processing completed."
  293. Write-Host ""
  294. }
  295. # we need to update all urls to /latest/en
  296. function PostProcessing-FixingSitemap {
  297. Write-Host -ForegroundColor Yellow "Post-processing sitemap.xml, adding latest/en to url"
  298. Write-Host ""
  299. $sitemapFile = "$($Settings.SiteDirectory)/en/sitemap.xml"
  300. # Read the content of the sitemap.xml file with UTF8 encoding
  301. $content = Get-Content $sitemapFile -Encoding UTF8
  302. # Replace DocsUrl with DocsUrl + latest/en
  303. $content = $content -replace $Settings.DocsUrl, "$($Settings.DocsUrl)/latest/en"
  304. # Write the updated content back to the sitemap.xml file with UTF8 encoding
  305. $content | Set-Content -Encoding UTF8 $sitemapFile
  306. Write-Host -ForegroundColor Green "Post-processing sitemap.xml completed."
  307. Write-Host ""
  308. }
  309. function PostProcessing-Fixing404AbsolutePath {
  310. Write-Host -ForegroundColor Yellow "Post-processing 404.html, adding version/en to url"
  311. Write-Host ""
  312. $file404 = "$($Settings.SiteDirectory)/en/404.html"
  313. $content = Get-Content $file404 -Encoding UTF8
  314. $keysToReplace = @("favicon.ico", "public/docfx.min.css", "public/main.css", "toc.html", "media/stride-logo-red.svg")
  315. foreach ($key in $keysToReplace) {
  316. $replacement = "/$($Settings.Version)/en/$key"
  317. $content = $content -replace $key, $replacement
  318. }
  319. $content = $content -replace "./public/main.js", "/$($Settings.Version)/en/public/main.js"
  320. $content = $content -replace "./public/docfx.min.js", "/$($Settings.Version)/en/public/docfx.min.js"
  321. $content = $content -replace '<a class="navbar-brand" href="index.html">', '<a class="navbar-brand" href="/">'
  322. $content | Set-Content -Encoding UTF8 $file404
  323. Write-Host -ForegroundColor Green "Post-processing 404.html completed."
  324. Write-Host ""
  325. }
  326. # Main script execution starts here
  327. $languages = Read-LanguageConfigurations
  328. Start-Transcript -Path $Settings.LogPath
  329. [bool]$isAllLanguages = $false
  330. if ($BuildAll)
  331. {
  332. $isAllLanguages = $true
  333. $API = $true
  334. $ReuseAPI = $false
  335. }
  336. else {
  337. $userInput = Get-UserInput
  338. [bool]$isEnLanguage = $userInput -ieq "en"
  339. [bool]$shouldRunLocalWebsite = $userInput -ieq "r"
  340. [bool]$isCanceled = $userInput -ieq "c"
  341. $isAllLanguages = $userInput -ieq "all"
  342. # Check if user input matches any non-English language build
  343. $selectedLanguage = $languages | Where-Object { $_.Code -eq $userInput -and $_.Enabled -and -not $_.IsPrimary }
  344. if ($selectedLanguage)
  345. {
  346. [bool]$shouldBuildSelectedLanguage = $true
  347. }
  348. # Ask if the user wants to include API
  349. if ($isEnLanguage -or $isAllLanguages -or $shouldBuildSelectedLanguage)
  350. {
  351. $API = Ask-IncludeAPI
  352. if ($API)
  353. {
  354. # Check for .yml files
  355. $ymlFiles = Get-ChildItem -Path "en/api/" -Filter "*.yml"
  356. if ($ymlFiles.Count -gt 0)
  357. {
  358. $ReuseAPI = Ask-UseExistingAPI
  359. }
  360. }
  361. } elseif ($isCanceled) {
  362. Write-Host -ForegroundColor Red "Operation canceled by user."
  363. Stop-Transcript
  364. Read-Host -Prompt "Press ENTER key to exit..."
  365. return
  366. } elseif ($shouldRunLocalWebsite) {
  367. Start-LocalWebsite
  368. return
  369. }
  370. }
  371. # Generate API doc
  372. if ($ReuseAPI)
  373. {
  374. Write-Host -ForegroundColor Green "Generating API documentation from existing mete data..."
  375. } elseif ($API)
  376. {
  377. $exitCode = Generate-APIDoc
  378. if($exitCode -ne 0)
  379. {
  380. Write-Error "Failed to generate API metadata. ExitCode: $exitCode"
  381. Stop-Transcript
  382. Read-Host -Prompt "Press any ENTER to exit..."
  383. return $exitCode
  384. }
  385. } else {
  386. Remove-APIDoc
  387. }
  388. Write-Host -ForegroundColor Green "Generating documentation..."
  389. Write-Host ""
  390. Write-Warning "Note that when building docs without API, you will get UidNotFound warnings and invalid references warnings"
  391. Write-Host ""
  392. if ($isEnLanguage -or $isAllLanguages)
  393. {
  394. $exitCode = Build-EnglishDoc
  395. if ($exitCode -ne 0)
  396. {
  397. Write-Error "Failed to build English documentation. ExitCode: $exitCode"
  398. Stop-Transcript
  399. Read-Host -Prompt "Press any ENTER to exit..."
  400. return $exitCode
  401. }
  402. PostProcessing-FixingSitemap
  403. PostProcessing-Fixing404AbsolutePath
  404. Copy-ExtraItems
  405. }
  406. # Build non-English language if selected or build all languages if selected
  407. if ($isAllLanguages) {
  408. Build-AllLanguagesDocs -Languages $languages
  409. } elseif ($selectedLanguage) {
  410. Build-NonEnglishDoc -SelectedLanguage $selectedLanguage
  411. }
  412. Stop-Transcript
  413. Read-Host -Prompt "Press any ENTER to exit..."