BuildDocs.ps1 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535
  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. # Build pdf files
  153. docfx pdf en/docfx.json -o $outputDirectory | Write-Host
  154. return $LastExitCode
  155. }
  156. function Build-NonEnglishDoc {
  157. param (
  158. $SelectedLanguage
  159. )
  160. if ($SelectedLanguage -and $SelectedLanguage.Code -ne 'en') {
  161. Write-Host "-------------------------------------------------------------------------------"
  162. Write-Host ""
  163. Write-Host -ForegroundColor Yellow "Start building $($SelectedLanguage.Name) documentation."
  164. Write-Host ""
  165. $langFolder = "$($SelectedLanguage.Code)$($Settings.TempDirectory)"
  166. if (Test-Path $langFolder) {
  167. Remove-Item $langFolder/* -recurse -Verbose
  168. }
  169. else {
  170. $discard = New-Item -Path $langFolder -ItemType Directory -Verbose
  171. }
  172. # Copy all files from en folder to the selected language folder, this way we can keep en files that are not translated
  173. Copy-Item en/* -Recurse $langFolder -Force
  174. # Get all translated files from the selected language folder
  175. $files = Get-ChildItem "$langFolder/$($Settings.ManualFolderName)/*.md" -Recurse -Force
  176. Write-Host "Start write files:"
  177. # Mark files as not translated if they are not in the toc.md file
  178. foreach ($file in $files)
  179. {
  180. if($file.ToString().Contains("toc.md")) {
  181. continue;
  182. }
  183. $data = Get-Content $file -Encoding UTF8
  184. for ($i = 0; $i -lt $data.Length; $i++)
  185. {
  186. $line = $data[$i];
  187. if ($line.length -le 0)
  188. {
  189. Write-Host $file
  190. $data[$i]="> [!WARNING]`r`n> " + $SelectedLanguage.NotTranslatedMessage + "`r`n"
  191. $data | Out-File -Encoding UTF8 $file
  192. break
  193. }
  194. }
  195. }
  196. Write-Host "End write files"
  197. $indexFile = $Settings.IndexFileName
  198. # overwrite en manual page with translated manual page
  199. if (Test-Path ($SelectedLanguage.Code + "/" + $indexFile)) {
  200. Copy-Item ($SelectedLanguage.Code + "/" + $indexFile) $langFolder -Force
  201. }
  202. else {
  203. Write-Warning "$($SelectedLanguage.Code)/"+ $indexFile +" not found. English version will be used."
  204. }
  205. # overwrite en manual pages with translated manual pages
  206. if (Test-Path ($SelectedLanguage.Code + "/" + $Settings.ManualFolderName)) {
  207. Copy-Item ($SelectedLanguage.Code + "/" + $Settings.ManualFolderName) -Recurse -Destination $langFolder -Force
  208. }
  209. else {
  210. Write-Warning "$($SelectedLanguage.Code)/$($Settings.ManualFolderName) not found."
  211. }
  212. # 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
  213. Copy-Item en/docfx.json $langFolder -Force
  214. $SiteDir = $Settings.SiteDirectory
  215. # we replace the en folder with the selected language folder in the docfx.json file
  216. (Get-Content $langFolder/docfx.json) -replace "$SiteDir/en","$SiteDir/$($SelectedLanguage.Code)" | Set-Content -Encoding UTF8 $langFolder/docfx.json
  217. $outputDirectory = "$($Settings.SiteDirectory)/$($SelectedLanguage.Code)"
  218. docfx build $langFolder/docfx.json -o $outputDirectory | Write-Host
  219. if (!$BuildAll) {
  220. Remove-Item $langFolder -Recurse -Verbose
  221. }
  222. PostProcessing-DocFxDocUrl -SelectedLanguage $SelectedLanguage
  223. Write-Host -ForegroundColor Green "$($SelectedLanguage.Name) documentation built."
  224. return $LastExitCode
  225. }
  226. }
  227. function Build-AllLanguagesDocs {
  228. param (
  229. [array]$Languages
  230. )
  231. foreach ($lang in $Languages) {
  232. if ($lang.Enabled -and -not $lang.IsPrimary) {
  233. Build-NonEnglishDoc -SelectedLanguage $lang
  234. if ($exitCode -ne 0)
  235. {
  236. Write-Error "Failed to build $($SelectedLanguage.Name) documentation. ExitCode: $exitCode"
  237. Stop-Transcript
  238. return $exitCode
  239. }
  240. }
  241. }
  242. }
  243. function PostProcessing-DocFxDocUrl {
  244. <#
  245. .SYNOPSIS
  246. 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.
  247. .DESCRIPTION
  248. 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.
  249. .PARAMETER SelectedLanguage
  250. The Language to find the relevant HTML and markdown Files
  251. .NOTES
  252. 1. This function assumes that the folder structure and naming conventions meet the specified conditions.
  253. 2. Progress is displayed interactively, and is suppressed in non-interactive sessions such as CI/CD pipelines.
  254. #>
  255. param (
  256. $SelectedLanguage
  257. )
  258. $translatedFiles = Get-ChildItem "$($SelectedLanguage.Code)/*.md" -Recurse -Force
  259. # Get a list of all HTML files in the _site/<language> directory
  260. $htmlFiles = Get-ChildItem "$($Settings.SiteDirectory)/$($SelectedLanguage.Code)/*.html" -Recurse
  261. # Get the relative paths of the translated files
  262. $relativeTranslatedFilesPaths = $translatedFiles | ForEach-Object { $_.FullName.Replace((Resolve-Path $SelectedLanguage.Code).Path + '\', '') }
  263. Write-Host -ForegroundColor Yellow "Post-processing docfx:docurl in $($htmlFiles.Count) files..."
  264. for ($i = 0; $i -lt $htmlFiles.Count; $i++) {
  265. $htmlFile = $htmlFiles[$i]
  266. # Get the relative path of the HTML file
  267. $relativeHtmlPath = $htmlFile.FullName.Replace((Resolve-Path "$($Settings.SiteDirectory)/$($SelectedLanguage.Code)").Path + '\', '').Replace('.html', '.md')
  268. # Read the content of the HTML file
  269. $content = Get-Content $htmlFile -Encoding UTF8
  270. # Define a regex pattern to match the meta tag with name="docfx:docurl"
  271. $pattern = '(<meta name="docfx:docurl" content=".*?)(/' + $SelectedLanguage.Code + $Settings.TempDirectory+ '/)(.*?">)'
  272. # Define a regex pattern to match the href attribute in the <a> tags
  273. $pattern2 = '(<a href=".*?)(/' + $SelectedLanguage.Code + $Settings.TempDirectory + '/)(.*?">)'
  274. # Check if the HTML file is from the $translatedFiles collection, if so, we will update the path to the
  275. # existing file in GitHub
  276. if ($relativeTranslatedFilesPaths -contains $relativeHtmlPath) {
  277. # Replace /<language>_tmp/ with /<language>/ in the content
  278. $content = $content -replace $pattern, "`${1}/$($SelectedLanguage.Code)/`${3}"
  279. $content = $content -replace $pattern2, "`${1}/$($SelectedLanguage.Code)/`${3}"
  280. } else {
  281. # Replace /<language>_tmp/ with /en/ in the content
  282. $content = $content -replace $pattern, '${1}/en/${3}'
  283. $content = $content -replace $pattern2, '${1}/en/${3}'
  284. }
  285. # Write the updated content back to the HTML file
  286. $content | Set-Content -Encoding UTF8 $htmlFile
  287. # Check if the script is running in an interactive session before writing progress
  288. # We don't want to write progress when running in a non-interactive session, such as in a build pipeline
  289. if ($host.UI.RawUI) {
  290. Write-Progress -Activity "Processing files" -Status "$i of $($htmlFiles.Count) processed" -PercentComplete (($i / $htmlFiles.Count) * 100)
  291. }
  292. }
  293. Write-Host ""
  294. Write-Host -ForegroundColor Green "Post-processing completed."
  295. Write-Host ""
  296. }
  297. # we need to update all urls to /latest/en
  298. function PostProcessing-FixingSitemap {
  299. Write-Host -ForegroundColor Yellow "Post-processing sitemap.xml, adding latest/en to url"
  300. Write-Host ""
  301. $sitemapFile = "$($Settings.SiteDirectory)/en/sitemap.xml"
  302. # Read the content of the sitemap.xml file with UTF8 encoding
  303. $content = Get-Content $sitemapFile -Encoding UTF8
  304. # Replace DocsUrl with DocsUrl + latest/en
  305. $content = $content -replace $Settings.DocsUrl, "$($Settings.DocsUrl)/latest/en"
  306. # Write the updated content back to the sitemap.xml file with UTF8 encoding
  307. $content | Set-Content -Encoding UTF8 $sitemapFile
  308. Write-Host -ForegroundColor Green "Post-processing sitemap.xml completed."
  309. Write-Host ""
  310. }
  311. function PostProcessing-Fixing404AbsolutePath {
  312. Write-Host -ForegroundColor Yellow "Post-processing 404.html, adding version/en to url"
  313. Write-Host ""
  314. $file404 = "$($Settings.SiteDirectory)/en/404.html"
  315. $content = Get-Content $file404 -Encoding UTF8
  316. $keysToReplace = @("favicon.ico", "public/docfx.min.css", "public/main.css", "toc.html", "media/stride-logo-red.svg")
  317. foreach ($key in $keysToReplace) {
  318. $replacement = "/$($Settings.Version)/en/$key"
  319. $content = $content -replace $key, $replacement
  320. }
  321. $content = $content -replace "./public/main.js", "/$($Settings.Version)/en/public/main.js"
  322. $content = $content -replace "./public/docfx.min.js", "/$($Settings.Version)/en/public/docfx.min.js"
  323. $content = $content -replace '<a class="navbar-brand" href="index.html">', '<a class="navbar-brand" href="/">'
  324. $content | Set-Content -Encoding UTF8 $file404
  325. Write-Host -ForegroundColor Green "Post-processing 404.html completed."
  326. Write-Host ""
  327. }
  328. # Main script execution starts here
  329. $languages = Read-LanguageConfigurations
  330. Start-Transcript -Path $Settings.LogPath
  331. [bool]$isAllLanguages = $false
  332. if ($BuildAll)
  333. {
  334. $isAllLanguages = $true
  335. $API = $true
  336. $ReuseAPI = $false
  337. }
  338. else {
  339. $userInput = Get-UserInput
  340. [bool]$isEnLanguage = $userInput -ieq "en"
  341. [bool]$shouldRunLocalWebsite = $userInput -ieq "r"
  342. [bool]$isCanceled = $userInput -ieq "c"
  343. $isAllLanguages = $userInput -ieq "all"
  344. # Check if user input matches any non-English language build
  345. $selectedLanguage = $languages | Where-Object { $_.Code -eq $userInput -and $_.Enabled -and -not $_.IsPrimary }
  346. if ($selectedLanguage)
  347. {
  348. [bool]$shouldBuildSelectedLanguage = $true
  349. }
  350. # Ask if the user wants to include API
  351. if ($isEnLanguage -or $isAllLanguages -or $shouldBuildSelectedLanguage)
  352. {
  353. $API = Ask-IncludeAPI
  354. if ($API)
  355. {
  356. # Check for .yml files
  357. $ymlFiles = Get-ChildItem -Path "en/api/" -Filter "*.yml"
  358. if ($ymlFiles.Count -gt 0)
  359. {
  360. $ReuseAPI = Ask-UseExistingAPI
  361. }
  362. }
  363. } elseif ($isCanceled) {
  364. Write-Host -ForegroundColor Red "Operation canceled by user."
  365. Stop-Transcript
  366. Read-Host -Prompt "Press ENTER key to exit..."
  367. return
  368. } elseif ($shouldRunLocalWebsite) {
  369. Start-LocalWebsite
  370. return
  371. }
  372. }
  373. # Generate API doc
  374. if ($ReuseAPI)
  375. {
  376. Write-Host -ForegroundColor Green "Generating API documentation from existing mete data..."
  377. } elseif ($API)
  378. {
  379. $exitCode = Generate-APIDoc
  380. if($exitCode -ne 0)
  381. {
  382. Write-Error "Failed to generate API metadata. ExitCode: $exitCode"
  383. Stop-Transcript
  384. Read-Host -Prompt "Press any ENTER to exit..."
  385. return $exitCode
  386. }
  387. } else {
  388. Remove-APIDoc
  389. }
  390. Write-Host -ForegroundColor Green "Generating documentation..."
  391. Write-Host ""
  392. Write-Warning "Note that when building docs without API, you will get UidNotFound warnings and invalid references warnings"
  393. Write-Host ""
  394. if ($isEnLanguage -or $isAllLanguages)
  395. {
  396. $exitCode = Build-EnglishDoc
  397. if ($exitCode -ne 0)
  398. {
  399. Write-Error "Failed to build English documentation. ExitCode: $exitCode"
  400. Stop-Transcript
  401. Read-Host -Prompt "Press any ENTER to exit..."
  402. return $exitCode
  403. }
  404. PostProcessing-FixingSitemap
  405. PostProcessing-Fixing404AbsolutePath
  406. Copy-ExtraItems
  407. }
  408. # Build non-English language if selected or build all languages if selected
  409. if ($isAllLanguages) {
  410. Build-AllLanguagesDocs -Languages $languages
  411. } elseif ($selectedLanguage) {
  412. Build-NonEnglishDoc -SelectedLanguage $selectedLanguage
  413. }
  414. Stop-Transcript
  415. Read-Host -Prompt "Press any ENTER to exit..."