BuildDocs.ps1 20 KB

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