diff --git a/CHANGELOG.md b/CHANGELOG.md index 081353d..d62b176 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,10 +18,20 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 generate the modules help files to support `Get-Help` for public commands. This task is dependent on the task `Generate_Markdown_For_Public_Commands` to have been run prior. -- Task `Clean_Markdown_Of_Public_Commands` which will remove metadata and - wrongly added parameters in the command markdown documentation. -- Private function `Remove-MarkdownMetadataBlock` that removes metadata from a +- Task `Clean_Markdown_Of_Public_Commands` which will edit the the command + markdown documentation. For example it will remove the `ProgressAction` + parameter that PlatyPS remove wrongly add (due to a bug). +- Task `Clean_Markdown_Metadata` which will remove the markdown metadata + block from the markdown documentation. The metadata block was used for + other tasks to know what type of content the markdown file contained. +- Task `Generate_Wiki_Sidebar` - This task will generate the GitHub Wiki + Repository sidebar based on the files present in the built documentation + folder (defaults to `./output/WikiOutput`). +- Public command `Remove-MarkdownMetadataBlock` that removes metadata from a Markdown file. +- Public command `New-GitHubWikiSidebar` generate the GitHub Wiki + Repository sidebar based on the files present in the built documentation + folder (defaults to `./output/WikiOutput`). - Private function `Remove-ParameterFromMarkdown` that removes a parameter from a commands markdown documentation. - Private function `Remove-EscapedMarkdownCode` that removes a escape sequences @@ -42,6 +52,20 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - The built module is now removed from the session when initiating a new build. The build pipeline is dogfooding functionality and leaving a previous version imported in the session do not use new code. +- Task `Generate_Wiki_Content` + - Support passing metadata trough the build configuration file (`build.yaml`). +- `New-DscResourceWikiPage` + - A new parameter `Metadata` that takes a hashtable of metadata. See + comment-based help for the format of the hashtable. +- `New-DscClassResourceWikiPage` + - A new parameter `Metadata` that takes a hashtable of metadata. See + comment-based help for the format of the hashtable. +- `New-DscCompositeResourceWikiPage` + - A new parameter `Metadata` that takes a hashtable of metadata. See + comment-based help for the format of the hashtable. +- `New-DscMofResourceWikiPage` + - A new parameter `Metadata` that takes a hashtable of metadata. See + comment-based help for the format of the hashtable. ### Fixed diff --git a/README.md b/README.md index 49a1d2f..1dbc5a4 100644 --- a/README.md +++ b/README.md @@ -79,7 +79,8 @@ Below is an example how the build task can be used when a repository is based on the [Sampler](https://github.com/gaelcolas/Sampler) project. >[!NOTE] This task is meant to be run after the tasks ->`Generate_Markdown_For_Public_Commands` and `Generate_Wiki_Content`. +>`Generate_Markdown_For_Public_Commands` and `Generate_Wiki_Content`, but +>before the task `Clean_Markdown_Metadata`. ```yaml BuildWorkflow: @@ -101,6 +102,40 @@ BuildWorkflow: - Publish_GitHub_Wiki_Content ``` +### `Clean_Markdown_Metadata` + +This build task runs the command `Remove-MarkdownMetadataBlock` on each markdown +file that is present in the folder provided by the parameter `DocOutputFolder` +(defaults to _./output/WikiContent_). + +See the command `Remove-MarkdownMetadataBlock` for more information. + +Below is an example how the build task can be used when a repository is +based on the [Sampler](https://github.com/gaelcolas/Sampler) project. + +>[!NOTE] This task is meant to be run after all tasks that generate of modifies +>markdown. + +```yaml +BuildWorkflow: + '.': + - build + + build: + - Clean + - Build_Module_ModuleBuilder + - Build_NestedModules_ModuleBuilder + - Create_changelog_release_output + - Generate_Wiki_Content + - Generate_Markdown_For_Public_Commands + - Clean_Markdown_Metadata + + publish: + - Publish_release_to_GitHub + - publish_module_to_gallery + - Publish_GitHub_Wiki_Content +``` + ### `Generate_Conceptual_Help` This build task runs the command `New-DscResourcePowerShellHelp`. @@ -302,6 +337,48 @@ BuildWorkflow: - Generate_Wiki_Content ``` +### `Generate_Wiki_Sidebar` + +This build task runs the command `New-GitHubWikiSidebar` (PlatyPS command) that +will generate markdown for the module's public commands. See the command +`New-GitHubWikiSidebar` for more information. + +It is possible to pass: + +- `DocOutputFolder` (default is `./output/WikiContent`) +- `DebugTask` (default is `$true`) + +They can be set either in parent scope, as an environment variable, or if +passed as a parameter to the build task. + +Below is an example how the build task can be used when a repository is +based on the [Sampler](https://github.com/gaelcolas/Sampler) project. + +>[!NOTE] This task is meant to be run after all the needed tasks that generate +> markdown documentation. It must be run before `Clean_Markdown_Metadata`. + +```yaml +BuildWorkflow: + '.': + - build + + build: + - Clean + - Build_Module_ModuleBuilder + - Build_NestedModules_ModuleBuilder + - Create_changelog_release_output + - Generate_Wiki_Content + - Generate_Markdown_For_Public_Commands + - Clean_Markdown_Of_Public_Commands + - Generate_Wiki_Sidebar + - Clean_Markdown_Metadata + + publish: + - Publish_release_to_GitHub + - publish_module_to_gallery + - Publish_GitHub_Wiki_Content +``` + ### `Publish_GitHub_Wiki_Content` This build task runs the command `Publish-WikiContent`. The task will only diff --git a/Resolve-Dependency.ps1 b/Resolve-Dependency.ps1 index 260b21b..17cc98e 100644 --- a/Resolve-Dependency.ps1 +++ b/Resolve-Dependency.ps1 @@ -50,9 +50,16 @@ Specifies to use ModuleFast instead of PowerShellGet to resolve dependencies faster. - .PARAMETER PSResourceGet - Specifies to use ModuleFast instead of PowerShellGet to resolve dependencies - faster. + .PARAMETER ModuleFastBleedingEdge + Specifies to use ModuleFast code that is in the ModuleFast's main branch + in its GitHub repository. The parameter UseModuleFast must also be set to + true. + + .PARAMETER UsePSResourceGet + Specifies to use the new PSResourceGet module instead of the (now legacy) PowerShellGet module. + + .PARAMETER PSResourceGetVersion + String specifying the module version for PSResourceGet if the `UsePSResourceGet` switch is utilized. .NOTES Load defaults for parameters values from Resolve-Dependency.psd1 if not @@ -114,13 +121,29 @@ param [System.Management.Automation.SwitchParameter] $UseModuleFast, + [Parameter()] + [System.Management.Automation.SwitchParameter] + $ModuleFastBleedingEdge, + + [Parameter()] + [System.String] + $ModuleFastVersion, + [Parameter()] [System.Management.Automation.SwitchParameter] $UsePSResourceGet, [Parameter()] [System.String] - $PSResourceGetVersion + $PSResourceGetVersion, + + [Parameter()] + [System.Management.Automation.SwitchParameter] + $UsePowerShellGetCompatibilityModule, + + [Parameter()] + [System.String] + $UsePowerShellGetCompatibilityModuleVersion ) try @@ -178,7 +201,7 @@ catch Write-Warning -Message "Error attempting to import Bootstrap's default parameters from '$resolveDependencyConfigPath': $($_.Exception.Message)." } -# Handles when both ModuleFast and PSResourceGet is configured or/and passed as parameter. +# Handle when both ModuleFast and PSResourceGet is configured or/and passed as parameter. if ($UseModuleFast -and $UsePSResourceGet) { Write-Information -MessageData 'Both ModuleFast and PSResourceGet is configured or/and passed as parameter.' -InformationAction 'Continue' @@ -193,39 +216,60 @@ if ($UseModuleFast -and $UsePSResourceGet) { $UseModuleFast = $false - Write-Information -MessageData 'Older PowerShell or Windows PowerShell being used, prefer PSResourceGet since ModuleFast is not supported on this version of PowerShell.' -InformationAction 'Continue' + Write-Information -MessageData 'Windows PowerShell or PowerShell <=7.1 is being used, prefer PSResourceGet since ModuleFast is not supported on this version of PowerShell.' -InformationAction 'Continue' } } -if ($UseModuleFast) +# Only bootstrap ModuleFast if it is not already imported. +if ($UseModuleFast -and -not (Get-Module -Name 'ModuleFast')) { try { + $moduleFastBootstrapScriptBlockParameters = @{} + + if ($ModuleFastBleedingEdge) + { + Write-Information -MessageData 'ModuleFast is configured to use Bleeding Edge (directly from ModuleFast''s main branch).' -InformationAction 'Continue' + + $moduleFastBootstrapScriptBlockParameters.UseMain = $true + } + elseif($ModuleFastVersion) + { + if ($ModuleFastVersion -notmatch 'v') + { + $ModuleFastVersion = 'v{0}' -f $ModuleFastVersion + } + + Write-Information -MessageData ('ModuleFast is configured to use version {0}.' -f $ModuleFastVersion) -InformationAction 'Continue' + + $moduleFastBootstrapScriptBlockParameters.Release = $ModuleFastVersion + } + else + { + Write-Information -MessageData 'ModuleFast is configured to use latest released version.' -InformationAction 'Continue' + } + + $moduleFastBootstrapUri = 'bit.ly/modulefast' # cSpell: disable-line + + Write-Debug -Message ('Using bootstrap script at {0}' -f $moduleFastBootstrapUri) + $invokeWebRequestParameters = @{ - Uri = 'bit.ly/modulefast' # cSpell: disable-line + Uri = $moduleFastBootstrapUri ErrorAction = 'Stop' } $moduleFastBootstrapScript = Invoke-WebRequest @invokeWebRequestParameters - <# - Using this method instead of the one mentioned in the instructions from - https://github.com/JustinGrote/ModuleFast to avoid the PSScriptAnalyzer - rule PSAvoidUsingInvokeExpression. - #> $moduleFastBootstrapScriptBlock = [ScriptBlock]::Create($moduleFastBootstrapScript) - <# - We could pass parameters to the bootstrap script when calling Invoke(). - But currently the default parameter values works just fine. - #> - $moduleFastBootstrapScriptBlock.Invoke() + & $moduleFastBootstrapScriptBlock @moduleFastBootstrapScriptBlockParameters } catch { - Write-Warning -Message ('ModuleFast could not be bootstrapped. Reverting to PowerShellGet. Error: {0}' -f $_.Exception.Message) + Write-Warning -Message ('ModuleFast could not be bootstrapped. Reverting to PSResourceGet. Error: {0}' -f $_.Exception.Message) $UseModuleFast = $false + $UsePSResourceGet = $true } } @@ -236,7 +280,7 @@ if ($UsePSResourceGet) # If PSResourceGet was used prior it will be locked and we can't replace it. if ((Test-Path -Path "$PSDependTarget/$psResourceGetModuleName" -PathType 'Container') -and (Get-Module -Name $psResourceGetModuleName)) { - Write-Information -MessageData ('{0} is already saved and loaded into the session. To refresh the module open a new session and resolve dependencies again.' -f $psResourceGetModuleName) -InformationAction 'Continue' + Write-Information -MessageData ('{0} is already bootstrapped and imported into the session. If there is a need to refresh the module, open a new session and resolve dependencies again.' -f $psResourceGetModuleName) -InformationAction 'Continue' } else { @@ -248,14 +292,17 @@ if ($UsePSResourceGet) { if (-not $PSResourceGetVersion) { - # Default version to use if non is specified in parameter or in configuration. - $PSResourceGetVersion = '0.9.0-rc1' + # Default to latest version if no version is passed in parameter or specified in configuration. + $psResourceGetUri = "https://www.powershellgallery.com/api/v2/package/$psResourceGetModuleName" + } + else + { + $psResourceGetUri = "https://www.powershellgallery.com/api/v2/package/$psResourceGetModuleName/$PSResourceGetVersion" } $invokeWebRequestParameters = @{ - # TODO: This should be hardcoded to a stable release in the future. # TODO: Should support proxy parameters passed to the script. - Uri = "https://www.powershellgallery.com/api/v2/package/$psResourceGetModuleName/$PSResourceGetVersion" + Uri = $psResourceGetUri OutFile = "$PSDependTarget/$psResourceGetModuleName.nupkg" # cSpell: ignore nupkg ErrorAction = 'Stop' } @@ -349,6 +396,7 @@ if ($UsePSResourceGet) } } +# Check if legacy PowerShellGet and PSDepend must be bootstrapped. if (-not ($UseModuleFast -or $UsePSResourceGet)) { if ($PSVersionTable.PSVersion.Major -le 5) @@ -466,8 +514,12 @@ if (-not ($UseModuleFast -or $UsePSResourceGet)) # Fail if the given PSGallery is not registered. $previousGalleryInstallationPolicy = (Get-PSRepository -Name $Gallery -ErrorAction 'Stop').Trusted + $updatedGalleryInstallationPolicy = $false + if ($previousGalleryInstallationPolicy -ne $true) { + $updatedGalleryInstallationPolicy = $true + # Only change policy if the repository is not trusted Set-PSRepository -Name $Gallery -InstallationPolicy 'Trusted' -ErrorAction 'Ignore' } @@ -475,6 +527,7 @@ if (-not ($UseModuleFast -or $UsePSResourceGet)) try { + # Check if legacy PowerShellGet and PSDepend must be used. if (-not ($UseModuleFast -or $UsePSResourceGet)) { Write-Progress -Activity 'Bootstrap:' -PercentComplete 25 -CurrentOperation 'Checking PowerShellGet' @@ -696,83 +749,130 @@ try $requiredModules = $requiredModules.GetEnumerator() | Where-Object -FilterScript { $_.Name -ne 'PSDependOptions' } - $modulesToSave = @( - 'PSDepend' # Always include PSDepend for backward compatibility. - ) - - foreach ($requiredModule in $requiredModules) + if ($UseModuleFast) { - # If the RequiredModules.psd1 entry is an Hashtable then special handling is needed. - if ($requiredModule.Value -is [System.Collections.Hashtable]) + Write-Progress -Activity 'Bootstrap:' -PercentComplete 90 -CurrentOperation 'Invoking ModuleFast' + + Write-Progress -Activity 'ModuleFast:' -PercentComplete 0 -CurrentOperation 'Restoring Build Dependencies' + + $modulesToSave = @( + 'PSDepend' # Always include PSDepend for backward compatibility. + ) + + if ($WithYAML) { - $saveModuleHashtable = @{ - ModuleName = $requiredModule.Name - } + $modulesToSave += 'PowerShell-Yaml' + } - if ($requiredModule.Value.Version -and $requiredModule.Value.Version -ne 'latest') + if ($UsePowerShellGetCompatibilityModule) + { + Write-Debug -Message 'PowerShellGet compatibility module is configured to be used.' + + # This is needed to ensure that the PowerShellGet compatibility module works. + $psResourceGetModuleName = 'Microsoft.PowerShell.PSResourceGet' + + if ($PSResourceGetVersion) { - $saveModuleHashtable.RequiredVersion = $requiredModule.Value.Version + $modulesToSave += ('{0}:[{1}]' -f $psResourceGetModuleName, $PSResourceGetVersion) } - - # ModuleFast does no support preview releases yet. - if ($UsePSResourceGet) + else { - if ($requiredModule.Value.Parameters.AllowPrerelease -eq $true) - { - $saveModuleHashtable.Prerelease = $true - } + $modulesToSave += $psResourceGetModuleName } - $modulesToSave += $saveModuleHashtable + $powerShellGetCompatibilityModuleName = 'PowerShellGet' + + if ($UsePowerShellGetCompatibilityModuleVersion) + { + $modulesToSave += ('{0}:[{1}]' -f $powerShellGetCompatibilityModuleName, $UsePowerShellGetCompatibilityModuleVersion) + } + else + { + $modulesToSave += $powerShellGetCompatibilityModuleName + } } - else + + foreach ($requiredModule in $requiredModules) { - if ($requiredModule.Value -eq 'latest') + # If the RequiredModules.psd1 entry is an Hashtable then special handling is needed. + if ($requiredModule.Value -is [System.Collections.Hashtable]) { - $modulesToSave += $requiredModule.Name + if (-not $requiredModule.Value.Version) + { + $requiredModuleVersion = 'latest' + } + else + { + $requiredModuleVersion = $requiredModule.Value.Version + } + + if ($requiredModuleVersion -eq 'latest') + { + $moduleNameSuffix = '' + + if ($requiredModule.Value.Parameters.AllowPrerelease -eq $true) + { + <# + Adding '!' to the module name indicate to ModuleFast + that is should also evaluate pre-releases. + #> + $moduleNameSuffix = '!' + } + + $modulesToSave += ('{0}{1}' -f $requiredModule.Name, $moduleNameSuffix) + } + else + { + $modulesToSave += ('{0}:[{1}]' -f $requiredModule.Name, $requiredModuleVersion) + } } else { - $modulesToSave += @{ - ModuleName = $requiredModule.Name - RequiredVersion = $requiredModule.Value + if ($requiredModule.Value -eq 'latest') + { + $modulesToSave += $requiredModule.Name + } + else + { + # Handle different nuget version operators already present. + if ($requiredModule.Value -match '[!|:|[|(|,|>|<|=]') + { + $modulesToSave += ('{0}{1}' -f $requiredModule.Name, $requiredModule.Value) + } + else + { + # Assuming the version is a fixed version. + $modulesToSave += ('{0}:[{1}]' -f $requiredModule.Name, $requiredModule.Value) + } } } } - } - if ($WithYAML) - { - $modulesToSave += 'PowerShell-Yaml' - } + Write-Debug -Message ("Required modules to retrieve plan for:`n{0}" -f ($modulesToSave | Out-String)) - if ($UseModuleFast) - { - Write-Progress -Activity 'Bootstrap:' -PercentComplete 90 -CurrentOperation 'Invoking ModuleFast' + $installModuleFastParameters = @{ + Destination = $PSDependTarget + DestinationOnly = $true + NoPSModulePathUpdate = $true + NoProfileUpdate = $true + Update = $true + Confirm = $false + } - Write-Progress -Activity 'ModuleFast:' -PercentComplete 0 -CurrentOperation 'Restoring Build Dependencies' + $moduleFastPlan = Install-ModuleFast -Specification $modulesToSave -Plan @installModuleFastParameters - $moduleFastPlan = $modulesToSave | Get-ModuleFastPlan + Write-Debug -Message ("Missing modules that need to be saved:`n{0}" -f ($moduleFastPlan | Out-String)) if ($moduleFastPlan) { # Clear all modules in plan from the current session so they can be fetched again. $moduleFastPlan.Name | Get-Module | Remove-Module -Force - $installModuleFastParameters = @{ - ModulesToInstall = $moduleFastPlan - Destination = $PSDependTarget - NoPSModulePathUpdate = $true - NoProfileUpdate = $true - Update = $true - Confirm = $false - } - - Install-ModuleFast @installModuleFastParameters + $moduleFastPlan | Install-ModuleFast @installModuleFastParameters } else { - Write-Verbose -Message 'All modules were already up to date' + Write-Verbose -Message 'All required modules were already up to date' } Write-Progress -Activity 'ModuleFast:' -PercentComplete 100 -CurrentOperation 'Dependencies restored' -Completed @@ -782,45 +882,82 @@ try { Write-Progress -Activity 'Bootstrap:' -PercentComplete 90 -CurrentOperation 'Invoking PSResourceGet' - $progressPercentage = 0 - - Write-Progress -Activity 'PSResourceGet:' -PercentComplete $progressPercentage -CurrentOperation 'Restoring Build Dependencies' - - $percentagePerModule = [System.Math]::Floor(100 / $modulesToSave.Length) + $modulesToSave = @( + @{ + Name = 'PSDepend' # Always include PSDepend for backward compatibility. + } + ) - foreach ($currentModule in $modulesToSave) + if ($WithYAML) { - Write-Progress -Activity 'PSResourceGet:' -PercentComplete $progressPercentage -CurrentOperation 'Restoring Build Dependencies' -Status ('Saving module {0}' -f $savePSResourceParameters.Name) - - $savePSResourceParameters = @{ - Path = $PSDependTarget - TrustRepository = $true - Confirm = $false + $modulesToSave += @{ + Name = 'PowerShell-Yaml' } + } - if ($currentModule -is [System.Collections.Hashtable]) + # Prepare hashtable that can be concatenated to the Save-PSResource parameters. + foreach ($requiredModule in $requiredModules) + { + # If the RequiredModules.psd1 entry is an Hashtable then special handling is needed. + if ($requiredModule.Value -is [System.Collections.Hashtable]) { - $savePSResourceParameters.Name = $currentModule.ModuleName + $saveModuleHashtable = @{ + Name = $requiredModule.Name + } - if ($currentModule.RequiredVersion) + if ($requiredModule.Value.Version -and $requiredModule.Value.Version -ne 'latest') { - $savePSResourceParameters.Version = $currentModule.RequiredVersion + $saveModuleHashtable.Version = $requiredModule.Value.Version } - if ($currentModule.Prerelease) + if ($requiredModule.Value.Parameters.AllowPrerelease -eq $true) { - $savePSResourceParameters.Prerelease = $currentModule.Prerelease + $saveModuleHashtable.Prerelease = $true } + + $modulesToSave += $saveModuleHashtable } else { - $savePSResourceParameters.Name = $currentModule + if ($requiredModule.Value -eq 'latest') + { + $modulesToSave += @{ + Name = $requiredModule.Name + } + } + else + { + $modulesToSave += @{ + Name = $requiredModule.Name + Version = $requiredModule.Value + } + } } + } + + $percentagePerModule = [System.Math]::Floor(100 / $modulesToSave.Length) + + $progressPercentage = 0 + + Write-Progress -Activity 'PSResourceGet:' -PercentComplete $progressPercentage -CurrentOperation 'Restoring Build Dependencies' + + foreach ($currentModule in $modulesToSave) + { + Write-Progress -Activity 'PSResourceGet:' -PercentComplete $progressPercentage -CurrentOperation 'Restoring Build Dependencies' -Status ('Saving module {0}' -f $savePSResourceParameters.Name) + + $savePSResourceParameters = @{ + Path = $PSDependTarget + TrustRepository = $true + Confirm = $false + } + + # Concatenate the module parameters to the Save-PSResource parameters. + $savePSResourceParameters += $currentModule # Modules that Sampler depend on that cannot be refreshed without a new session. - $skipModule = @('powershell-yaml') + $skipModule = @('PowerShell-Yaml') - if ($savePSResourceParameters.Name -in $skipModule -and (Get-Module -Name 'powershell-yaml')) + if ($savePSResourceParameters.Name -in $skipModule -and (Get-Module -Name $savePSResourceParameters.Name)) { Write-Progress -Activity 'PSResourceGet:' -PercentComplete $progressPercentage -CurrentOperation 'Restoring Build Dependencies' -Status ('Skipping module {0}' -f $savePSResourceParameters.Name) @@ -862,6 +999,10 @@ try Write-Progress -Activity 'PSDepend:' -PercentComplete 100 -CurrentOperation 'Dependencies restored' -Completed } } + else + { + Write-Warning -Message "The dependency file '$DependencyFile' could not be found." + } Write-Progress -Activity 'Bootstrap:' -PercentComplete 100 -CurrentOperation 'Bootstrap complete' -Completed } @@ -905,10 +1046,10 @@ finally Register-PSRepository @registerPSRepositoryParameters } - # Only try to revert installation policy if the repository exist - if ((Get-PSRepository -Name $Gallery -ErrorAction 'SilentlyContinue')) + if ($updatedGalleryInstallationPolicy -eq $true -and $previousGalleryInstallationPolicy -ne $true) { - if ($previousGalleryInstallationPolicy -ne $true) + # Only try to revert installation policy if the repository exist + if ((Get-PSRepository -Name $Gallery -ErrorAction 'SilentlyContinue')) { # Reverting the Installation Policy for the given gallery if it was not already trusted Set-PSRepository -Name $Gallery -InstallationPolicy 'Untrusted' diff --git a/Resolve-Dependency.psd1 b/Resolve-Dependency.psd1 index 2cd8513..07945f8 100644 --- a/Resolve-Dependency.psd1 +++ b/Resolve-Dependency.psd1 @@ -3,8 +3,13 @@ AllowPrerelease = $false WithYAML = $true + #UseModuleFast = $true + #ModuleFastVersion = '0.1.2' + #ModuleFastBleedingEdge = $true + UsePSResourceGet = $true - PSResourceGetVersion = '1.0.0' + #PSResourceGetVersion = '1.0.1' + UsePowerShellGetCompatibilityModule = $true - UsePowerShellGetCompatibilityModuleVersion = '3.0.22-beta22' + UsePowerShellGetCompatibilityModuleVersion = '3.0.23-beta23' } diff --git a/build.yaml b/build.yaml index 564ff5e..ec0a5ed 100644 --- a/build.yaml +++ b/build.yaml @@ -39,7 +39,9 @@ BuildWorkflow: - Generate_Wiki_Content - Generate_Markdown_For_Public_Commands - Generate_External_Help_File_For_Public_Commands + - Generate_Wiki_Sidebar - Clean_Markdown_Of_Public_Commands + - Clean_Markdown_Metadata pack: - build @@ -126,5 +128,26 @@ GitHubConfig: # DscResource.DocGenerator Configuration # #################################################### DscResource.DocGenerator: + Generate_Conceptual_Help: + MarkdownCodeRegularExpression: + - '\`(.+?)\`' # Match inline code-block + - '\\(\\)' # Match escaped backslash + - '\[[^\[]+\]\((.+?)\)' # Match markdown URL + - '_(.+?)_' # Match Italic (underscore) + - '\*\*(.+?)\*\*' # Match bold + - '\*(.+?)\*' # Match Italic (asterisk) Publish_GitHub_Wiki_Content: Debug: true + Generate_Wiki_Content: + MofResourceMetadata: + Type: MofResource + Category: Resources + ClassResourceMetadata: + Type: ClassResource + Category: Resources + CompositeResourceMetadata: + Type: CompositeResource + Category: Resources + Generate_Wiki_Sidebar: + Debug: false + AlwaysOverwrite: true diff --git a/source/Private/New-DscClassResourceWikiPage.ps1 b/source/Private/New-DscClassResourceWikiPage.ps1 index 920ee8d..eb9c697 100644 --- a/source/Private/New-DscClassResourceWikiPage.ps1 +++ b/source/Private/New-DscClassResourceWikiPage.ps1 @@ -20,6 +20,9 @@ The path to the root of the built DSC resource module, e.g. 'output/MyResource/1.0.0'. + .PARAMETER Metadata + Specifies metadata that is added to the markdown file. + .PARAMETER Force Overwrites any existing file when outputting the generated content. @@ -30,6 +33,19 @@ -OutputPath C:\repos\MyResource\output\WikiContent This example shows how to generate wiki documentation for a specific module. + + .EXAMPLE + New-DscClassResourceWikiPage ` + -SourcePath C:\repos\MyResource\source ` + -BuiltModulePath C:\repos\MyResource\output\MyResource\1.0.0 ` + -OutputPath C:\repos\MyResource\output\WikiContent ` + -Metadata @{ + Type = 'ClassResource' + Category = 'Class-based resources' + } + + This example shows how to generate wiki documentation for a specific module + and passing in metadata for the markdown files. #> function New-DscClassResourceWikiPage { @@ -49,6 +65,10 @@ function New-DscClassResourceWikiPage [System.String] $BuiltModulePath, + [Parameter()] + [System.Collections.Hashtable] + $Metadata, + [Parameter()] [System.Management.Automation.SwitchParameter] $Force @@ -83,6 +103,21 @@ function New-DscClassResourceWikiPage $output = New-Object -TypeName 'System.Text.StringBuilder' + # Add metadata to the top of the file. + if ($Metadata) + { + $null = $output.AppendLine('---') + + foreach ($key in ($Metadata.Keys | Sort-Object)) + { + $null = $output.AppendLine("$($key): $($Metadata.$key)") + } + + $null = $output.AppendLine('---') + $null = $output.AppendLine() + } + + # Add the documentation for the resource. $null = $output.AppendLine("# $($dscResourceAst.Name)") $null = $output.AppendLine() $null = $output.AppendLine('## Parameters') diff --git a/source/Private/New-DscCompositeResourceWikiPage.ps1 b/source/Private/New-DscCompositeResourceWikiPage.ps1 index 474dd92..c8c6938 100644 --- a/source/Private/New-DscCompositeResourceWikiPage.ps1 +++ b/source/Private/New-DscCompositeResourceWikiPage.ps1 @@ -20,6 +20,9 @@ The path to the root of the built DSC resource module, e.g. 'output/MyResource/1.0.0'. + .PARAMETER Metadata + Specifies metadata that is added to the markdown file. + .PARAMETER Force Overwrites any existing file when outputting the generated content. @@ -30,6 +33,19 @@ -OutputPath C:\repos\MyResource\output\WikiContent This example shows how to generate wiki documentation for a specific module. + + .EXAMPLE + New-DscCompositeResourceWikiPage ` + -SourcePath C:\repos\MyResource\source ` + -BuiltModulePath C:\repos\MyResource\output\MyResource\1.0.0 ` + -OutputPath C:\repos\MyResource\output\WikiContent ` + -Metadata @{ + Type = 'CompositeResource' + Category = 'Composites resources' + } + + This example shows how to generate wiki documentation for a specific module + and passing in metadata for the markdown files. #> function New-DscCompositeResourceWikiPage { @@ -50,6 +66,10 @@ function New-DscCompositeResourceWikiPage [System.String] $BuiltModulePath, + [Parameter()] + [System.Collections.Hashtable] + $Metadata, + [Parameter()] [System.Management.Automation.SwitchParameter] $Force @@ -76,6 +96,21 @@ function New-DscCompositeResourceWikiPage $output = New-Object -TypeName System.Text.StringBuilder + # Add metadata to the top of the file. + if ($Metadata) + { + $null = $output.AppendLine('---') + + foreach ($key in ($Metadata.Keys | Sort-Object)) + { + $null = $output.AppendLine("$($key): $($Metadata.$key)") + } + + $null = $output.AppendLine('---') + $null = $output.AppendLine() + } + + # Add the documentation for the resource. $null = $output.AppendLine("# $($compositeSchemaObject.Name)") $null = $output.AppendLine('') $null = $output.AppendLine('## Parameters') diff --git a/source/Private/New-DscMofResourceWikiPage.ps1 b/source/Private/New-DscMofResourceWikiPage.ps1 index 5fd10ac..7933ac7 100644 --- a/source/Private/New-DscMofResourceWikiPage.ps1 +++ b/source/Private/New-DscMofResourceWikiPage.ps1 @@ -16,6 +16,9 @@ The path to the root of the DSC resource module (where the PSD1 file is found, not the folder for and individual DSC resource). + .PARAMETER Metadata + Specifies metadata that is added to the markdown file. + .PARAMETER Force Overwrites any existing file when outputting the generated content. @@ -25,6 +28,18 @@ -OutputPath C:\repos\MyResource\output\WikiContent This example shows how to generate wiki documentation for a specific module. + + .EXAMPLE + New-DscMofResourceWikiPage ` + -SourcePath C:\repos\MyResource\source ` + -OutputPath C:\repos\MyResource\output\WikiContent ` + -Metadata @{ + Type = 'MofResource' + Category = 'MOF-based resources' + } + + This example shows how to generate wiki documentation for a specific module + and passing in metadata for the markdown files. #> function New-DscMofResourceWikiPage { @@ -41,6 +56,10 @@ function New-DscMofResourceWikiPage [System.String] $SourcePath, + [Parameter()] + [System.Collections.Hashtable] + $Metadata, + [Parameter()] [System.Management.Automation.SwitchParameter] $Force @@ -78,6 +97,21 @@ function New-DscMofResourceWikiPage $output = New-Object -TypeName System.Text.StringBuilder + # Add metadata to the top of the file. + if ($Metadata) + { + $null = $output.AppendLine('---') + + foreach ($key in ($Metadata.Keys | Sort-Object)) + { + $null = $output.AppendLine("$($key): $($Metadata.$key)") + } + + $null = $output.AppendLine('---') + $null = $output.AppendLine() + } + + # Add the documentation for the resource. $null = $output.AppendLine("# $($resourceSchema.FriendlyName)") $null = $output.AppendLine('') $null = $output.AppendLine('## Parameters') diff --git a/source/Private/Remove-MarkdownMetadataBlock.ps1 b/source/Private/Remove-MarkdownMetadataBlock.ps1 deleted file mode 100644 index c29f764..0000000 --- a/source/Private/Remove-MarkdownMetadataBlock.ps1 +++ /dev/null @@ -1,44 +0,0 @@ -<# - .SYNOPSIS - Removes metadata from a Markdown file. - - .DESCRIPTION - The Remove-MarkdownMetadataBlock function removes metadata from a Markdown file. - It searches for a metadata marker ('---') and removes the content between - the marker and the next occurrence of the marker. - - .PARAMETER FilePath - Specifies the path to the Markdown file from which the metadata should be removed. - - .EXAMPLE - Remove-MarkdownMetadataBlock -FilePath 'C:\Path\To\File.md' - - Removes the metadata from the specified Markdown file. -#> - -function Remove-MarkdownMetadataBlock -{ - [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseShouldProcessForStateChangingFunctions', '', Justification = 'This function is a private helper function and is not exported publicly.')] - [CmdletBinding()] - param - ( - [Parameter(Mandatory = $true, ValueFromPipeline = $true)] - [ValidateScript({Test-Path $_ -PathType 'Leaf'})] - [System.IO.FileInfo] - $FilePath - ) - - process - { - $content = Get-Content -Path $FilePath.FullName -Raw - - $metadataPattern = '(?s)---.*?---[\r|\n]*' - - if ($content -match $metadataPattern) - { - $content = $content -replace $metadataPattern - - Set-Content -Path $FilePath.FullName -Value $content - } - } -} diff --git a/source/Private/Task.Clean_Markdown_Metadata.build.ps1 b/source/Private/Task.Clean_Markdown_Metadata.build.ps1 new file mode 100644 index 0000000..09916b2 --- /dev/null +++ b/source/Private/Task.Clean_Markdown_Metadata.build.ps1 @@ -0,0 +1,15 @@ +<# + .SYNOPSIS + This is the alias to the build task Clean_Markdown_Metadata script file. + + .DESCRIPTION + This makes available the alias 'Task.Clean_Markdown_Metadata' that is + exported in the module manifest so that the build task can be correctly + imported using for example Invoke-Build. + + .NOTES + This is using the pattern lined out in the Invoke-Build repository + https://github.com/nightroman/Invoke-Build/tree/master/Tasks/Import. +#> + +Set-Alias -Name 'Task.Clean_Markdown_Metadata' -Value "$PSScriptRoot/tasks/Clean_Markdown_Metadata.build.ps1" diff --git a/source/Private/Task.Generate_Wiki_Sidebar.ps1 b/source/Private/Task.Generate_Wiki_Sidebar.ps1 new file mode 100644 index 0000000..f2df7a6 --- /dev/null +++ b/source/Private/Task.Generate_Wiki_Sidebar.ps1 @@ -0,0 +1,15 @@ +<# + .SYNOPSIS + This is the alias to the build task Generate_Wiki_Sidebar's script file. + + .DESCRIPTION + This makes available the alias 'Task.Generate_Wiki_Sidebar' that is + exported in the module manifest so that the build task can be correctly + imported using for example Invoke-Build. + + .NOTES + This is using the pattern lined out in the Invoke-Build repository + https://github.com/nightroman/Invoke-Build/tree/master/Tasks/Import. +#> + +Set-Alias -Name 'Task.Generate_Wiki_Sidebar' -Value "$PSScriptRoot/tasks/Generate_Wiki_Sidebar.build.ps1" diff --git a/source/Public/Edit-CommandDocumentation.ps1 b/source/Public/Edit-CommandDocumentation.ps1 index bf6fe1b..6f19356 100644 --- a/source/Public/Edit-CommandDocumentation.ps1 +++ b/source/Public/Edit-CommandDocumentation.ps1 @@ -61,8 +61,6 @@ Get-MarkdownMetadata -Path $($FilePath.FullName) -ErrorAction 'Stop' { Write-Information -MessageData "Cleaning documentation of '$($FilePath.BaseName)'." -InformationAction 'Continue' - Remove-MarkdownMetadataBlock -FilePath $FilePath - <# Remove ProgressAction parameter from the documentation. The parameter ProgressAction was introduced in PS 7.4 and is not diff --git a/source/Public/New-DscResourceWikiPage.ps1 b/source/Public/New-DscResourceWikiPage.ps1 index 3823dfe..a269f6a 100644 --- a/source/Public/New-DscResourceWikiPage.ps1 +++ b/source/Public/New-DscResourceWikiPage.ps1 @@ -34,6 +34,9 @@ .PARAMETER Force Overwrites any existing file when outputting the generated content. + .PARAMETER Metadata + The metadata for the DSC resource markdown files. + .EXAMPLE New-DscResourceWikiPage ` -SourcePath C:\repos\MyResource\source ` @@ -41,6 +44,29 @@ -OutputPath C:\repos\MyResource\output\WikiContent This example shows how to generate wiki documentation for a specific module. + + .EXAMPLE + New-DscResourceWikiPage ` + -SourcePath C:\repos\MyResource\source ` + -BuiltModulePath C:\repos\MyResource\output\MyResource\1.0.0 ` + -OutputPath C:\repos\MyResource\output\WikiContent ` + -Metadata @{ + MofResourceMetadata = @{ + Type = 'MofResource' + Category = 'MOF-based resources' + } + ClassResourceMetadata = @{ + Type = 'ClassResource' + Category = 'Class-based resources' + } + CompositeResourceMetadata = @{ + Type = 'CompositeResource' + Category = 'Composites resources' + } + } + + This example shows how to generate wiki documentation for a specific module + and passing in metadata for the markdown files. #> function New-DscResourceWikiPage { @@ -60,6 +86,10 @@ function New-DscResourceWikiPage [System.String] $BuiltModulePath, + [Parameter()] + [System.Collections.Hashtable] + $Metadata, + [Parameter()] [System.Management.Automation.SwitchParameter] $Force @@ -68,12 +98,29 @@ function New-DscResourceWikiPage $newDscMofResourceWikiPageParameters = @{ OutputPath = $OutputPath SourcePath = $SourcePath + Metadata = $Metadata.MofResourceMetadata Force = $Force } New-DscMofResourceWikiPage @newDscMofResourceWikiPageParameters - New-DscClassResourceWikiPage @PSBoundParameters + $newDscClassResourceWikiPageParameters = @{ + OutputPath = $OutputPath + SourcePath = $SourcePath + BuiltModulePath = $BuiltModulePath + Metadata = $Metadata.ClassResourceMetadata + Force = $Force + } + + New-DscClassResourceWikiPage @newDscClassResourceWikiPageParameters + + $newDscCompositeResourceWikiPageParameters = @{ + OutputPath = $OutputPath + SourcePath = $SourcePath + BuiltModulePath = $BuiltModulePath + Metadata = $Metadata.CompositeResourceMetadata + Force = $Force + } - New-DscCompositeResourceWikiPage @PSBoundParameters + New-DscCompositeResourceWikiPage @newDscCompositeResourceWikiPageParameters } diff --git a/source/Public/New-GitHubWikiSidebar.ps1 b/source/Public/New-GitHubWikiSidebar.ps1 new file mode 100644 index 0000000..c81ffe7 --- /dev/null +++ b/source/Public/New-GitHubWikiSidebar.ps1 @@ -0,0 +1,219 @@ + +<# + .SYNOPSIS + Creates a GitHub Wiki sidebar based on existing markdown files and their metadata. + + .DESCRIPTION + This command creates a new GitHub wiki sidebar file with the specified output + path and sidebar file name. The sidebar is created based on existing markdown + files and their metadata + + .PARAMETER DocumentationPath + Specifies the FileInfo object of the markdown file containing the documentation + being edited. This parameter is mandatory. + + .PARAMETER OutputPath + Specifies the output path where the sidebar file will be created. + + .PARAMETER SidebarFileName + Specifies the name of the sidebar file. The default value is '_Sidebar.md'. + + .PARAMETER ReplaceExisting + Specifies whether to force the creation of the sidebar even if it already exists. + By default, if the sidebar file already exists, the function will not overwrite it. + + .PARAMETER Force + Specifies that the sidebar should be created without any confirmation. + + .EXAMPLE + New-GitHubWikiSidebar -OutputPath 'C:\Wiki' -SidebarFileName 'CustomSidebar.md' + + Creates a new GitHub wiki sidebar file named 'CustomSidebar.md' in the 'C:\Wiki' + directory. + + .EXAMPLE + New-GitHubWikiSidebar -DocumentationPath './output/WikiOutput' -Force + + Creates a GitHub Wiki sidebar using default path and filename. The sidebar + will be created even if it already exists. +#> +function New-GitHubWikiSidebar +{ + [CmdletBinding(SupportsShouldProcess = $true, ConfirmImpact = 'High')] + param + ( + [Parameter(Mandatory = $true)] + [ValidateScript({Test-Path $_ -PathType 'Container'})] + [System.String] + $DocumentationPath, + + [Parameter()] + [ValidateScript({Test-Path $_ -PathType 'Container'})] + [System.String] + $OutputPath, + + [Parameter()] + [System.String] + $SidebarFileName = '_Sidebar.md', + + [Parameter()] + [System.Management.Automation.SwitchParameter] + $ReplaceExisting, + + [Parameter()] + [System.Management.Automation.SwitchParameter] + $Force + ) + + if ($Force.IsPresent -and -not $Confirm) + { + $ConfirmPreference = 'None' + } + + if (-not $OutputPath) + { + $OutputPath = $DocumentationPath + } + + $sidebarFilePath = Join-Path -Path $OutputPath -ChildPath $SidebarFileName + + <# + If the sidebar file already exists, don't overwrite it unless the -Force + parameter is used. This is to prevent overwriting if there are already a + sidebar that was created or copied to WikiOutput by another task, e.g. + Generate_Wiki_Content that copies the content of the WikiSource folder + to WikiOutput. + #> + if (-not $ReplaceExisting -and (Test-Path -Path $sidebarFilePath)) + { + Write-Warning "Sidebar file '$sidebarFilePath' already exists. Leaving it unchanged. Use -ReplaceExisting to overwrite." + + return + } + + $verboseDescriptionMessage = $script:localizedData.NewGitHubWikiSidebar_ShouldProcessVerboseDescription -f $sidebarFilePath + $verboseWarningMessage = $script:localizedData.NewGitHubWikiSidebar_ShouldProcessVerboseWarning -f $sidebarFilePath, $DocumentationPath + $captionMessage = $script:localizedData.NewGitHubWikiSidebar_ShouldProcessCaption + + if (-not $PSCmdlet.ShouldProcess($verboseDescriptionMessage, $verboseWarningMessage, $captionMessage)) + { + return + } + + # cSpell: disable-next-line + $markdownFiles = Get-ChildItem -Path "$DocumentationPath/*.md" -Exclude @('_[Ss]idebar.md', '_[Ff]ooter.md') + + $sidebarCategories = @{} + + foreach ($file in $markdownFiles) + { + Write-Debug -Message "Processing file '$($file.FullName)'." + + $getMarkdownMetadataScript = @" +Get-MarkdownMetadata -Path $($file.FullName) -ErrorAction 'Stop' +"@ + + Write-Debug -Message $getMarkdownMetadataScript + + $getMarkdownMetadataScriptBlock = [ScriptBlock]::Create($getMarkdownMetadataScript) + + $pwshPath = (Get-Process -Id $PID).Path + + <# + The scriptblock is run in a separate process to avoid conflicts with + other modules that are loaded in the current process. + #> + $markdownMetadata = & $pwshPath -Command $getMarkdownMetadataScriptBlock -ExecutionPolicy 'ByPass' -NoProfile + + # If the category doesn't exist in the metadata, add it to the default category General. + if (-not $markdownMetadata -or -not $markdownMetadata.ContainsKey('Category')) + { + $markdownMetadata = @{ + Category = 'General' + } + } + + Write-Information -MessageData "Found documentation '$($file.BaseName)' of type '$($markdownMetadata.Type)' in category '$($markdownMetadata.Category)'." -InformationAction 'Continue' + + if (-not $sidebarCategories.ContainsKey($markdownMetadata.Category)) + { + $sidebarCategories[$markdownMetadata.Category] = @() + } + + $sidebarCategories[$markdownMetadata.Category] += $file.BaseName + } + + $output = New-Object -TypeName 'System.Text.StringBuilder' + + # Always put link to Home at the top of the file. + if ($sidebarCategories.ContainsKey('General')) + { + if ($sidebarCategories.General -contains 'Home') + { + $null = $output.AppendLine('[Home](Home)') + $null = $output.AppendLine() + } + } + + # Always put category General at the top of the list. + if ($sidebarCategories.ContainsKey('General')) + { + $sortedListItem = $sidebarCategories.General | + Where-Object -FilterScript { + $_ -ne 'Home' + } | Sort-Object + + if ($sortedListItem.Count -gt 0) + { + $null = $output.AppendLine('### General') + $null = $output.AppendLine() + + foreach ($link in $sortedListItem) + { + $null = $output.AppendLine('- [' + $link + '](' + $link + ')') + } + + $null = $output.AppendLine() + } + } + + $sortedCategories = $sidebarCategories.Keys | + Where-Object -FilterScript { + $_ -ne 'General' + } | Sort-Object + + foreach ($category in $sortedCategories) + { + $null = $output.AppendLine("### $category") + $null = $output.AppendLine() + + foreach ($link in $sidebarCategories.$category | Sort-Object) + { + $null = $output.AppendLine('- [' + $link + '](' + $link + ')') + } + + $null = $output.AppendLine() + } + + $outputToWrite = $output.ToString() + $outputToWrite = $outputToWrite -replace '[\r|\n]+$' # Removes all blank rows and whitespace at the end + $outputToWrite = $outputToWrite -replace '\r?\n', "`r`n" # Normalize to CRLF + $outputToWrite = $outputToWrite -replace '[ ]+\r\n', "`r`n" # Remove indentation from blank rows + + $outFileParameters = @{ + InputObject = $outputToWrite + FilePath = $sidebarFilePath + Force = $ReplaceExisting + } + + if ($PSVersionTable.PSVersion -lt '6.0') + { + $outFileParameters.Encoding = 'UTF8' + } + else + { + $outFileParameters.Encoding = [System.Text.Encoding]::UTF8 + } + + $null = Out-File @outFileParameters +} diff --git a/source/Public/Remove-MarkdownMetadataBlock.ps1 b/source/Public/Remove-MarkdownMetadataBlock.ps1 new file mode 100644 index 0000000..f0e8c25 --- /dev/null +++ b/source/Public/Remove-MarkdownMetadataBlock.ps1 @@ -0,0 +1,62 @@ +<# + .SYNOPSIS + Removes metadata from a Markdown file. + + .DESCRIPTION + The Remove-MarkdownMetadataBlock function removes metadata from a Markdown file. + It searches for a metadata marker ('---') and removes the content between + the marker and the next occurrence of the marker. + + .PARAMETER FilePath + Specifies the path to the Markdown file from which the metadata should be removed. + + .PARAMETER Force + Specifies that the sidebar should be created without any confirmation. + + .EXAMPLE + Remove-MarkdownMetadataBlock -FilePath 'C:\Path\To\File.md' + + Removes the metadata from the specified Markdown file. +#> + +function Remove-MarkdownMetadataBlock +{ + [CmdletBinding(SupportsShouldProcess = $true, ConfirmImpact = 'High')] + param + ( + [Parameter(Mandatory = $true, ValueFromPipeline = $true)] + [ValidateScript({Test-Path $_ -PathType 'Leaf'})] + [System.IO.FileInfo] + $FilePath, + + [Parameter()] + [System.Management.Automation.SwitchParameter] + $Force + ) + + process + { + if ($Force.IsPresent -and -not $Confirm) + { + $ConfirmPreference = 'None' + } + + $content = Get-Content -Path $FilePath.FullName -Raw + + $metadataPattern = '(?s)---.*?---[\r|\n]*' + + if ($content -match $metadataPattern) + { + $verboseDescriptionMessage = $script:localizedData.RemoveMarkdownMetadataBlock_ShouldProcessVerboseDescription -f $FilePath.FullName + $verboseWarningMessage = $script:localizedData.RemoveMarkdownMetadataBlock_ShouldProcessVerboseWarning -f $FilePath.FullName + $captionMessage = $script:localizedData.RemoveMarkdownMetadataBlock_ShouldProcessCaption + + if ($PSCmdlet.ShouldProcess($verboseDescriptionMessage, $verboseWarningMessage, $captionMessage)) + { + $content = $content -replace $metadataPattern + + Set-Content -Path $FilePath.FullName -Value $content + } + } + } +} diff --git a/source/en-US/DscResource.DocGenerator.strings.psd1 b/source/en-US/DscResource.DocGenerator.strings.psd1 index 7fa010f..22e8806 100644 --- a/source/en-US/DscResource.DocGenerator.strings.psd1 +++ b/source/en-US/DscResource.DocGenerator.strings.psd1 @@ -42,4 +42,16 @@ ConvertFrom-StringData @' InvokeGitCommandDebug = Command: git {0} InvokeGitWorkingDirectoryDebug = git Working Directory: '{0}' ParsingOutCommentBasedHelpBlock = Parsing out only the comment-based help block from the source file. + + ## Remove-MarkdownMetadataBlock + RemoveMarkdownMetadataBlock_ShouldProcessVerboseDescription = Removing markdown metadata from markdown file '{0}'. + RemoveMarkdownMetadataBlock_ShouldProcessVerboseWarning = Are you sure you want to removing markdown metadata from markdown file '{0}'? + # This string shall not end with full stop (.) since it is used as a title of ShouldProcess messages. + RemoveMarkdownMetadataBlock_ShouldProcessCaption = Remove markdown metadata from file + + ## New-GitHubWikiSidebar + NewGitHubWikiSidebar_ShouldProcessVerboseDescription = Creating GitHub Wiki Sidebar '{0}'. + NewGitHubWikiSidebar_ShouldProcessVerboseWarning = Are you sure you want to create a GitHub Wiki Sidebar '{0}' based on the markdown files in '{1}'? + # This string shall not end with full stop (.) since it is used as a title of ShouldProcess messages. + NewGitHubWikiSidebar_ShouldProcessCaption = Create GitHub Wiki Sidebar '@ diff --git a/source/tasks/Clean_Markdown_Metadata.build.ps1 b/source/tasks/Clean_Markdown_Metadata.build.ps1 new file mode 100644 index 0000000..921054a --- /dev/null +++ b/source/tasks/Clean_Markdown_Metadata.build.ps1 @@ -0,0 +1,95 @@ +<# + .SYNOPSIS + This is a build task that clean markdown meta data from markdown documentation. + + .PARAMETER ProjectPath + The root path to the project. Defaults to $BuildRoot. + + .PARAMETER OutputDirectory + The base directory of all output. Defaults to folder 'output' relative to + the $BuildRoot. + + .PARAMETER BuiltModuleSubdirectory + Sub folder where you want to build the Module to (instead of $OutputDirectory/$ModuleName). + This is especially useful when you want to build DSC Resources, but you don't want the + `Get-DscResource` command to find several instances of the same DSC Resources because + of the overlapping $Env:PSmodulePath (`$buildRoot/output` for the built module and `$buildRoot/output/RequiredModules`). + + In most cases I would recommend against setting $BuiltModuleSubdirectory. + + .PARAMETER VersionedOutputDirectory + Whether the Module is built with its versioned Subdirectory, as you would see it on a System. + For instance, if VersionedOutputDirectory is $true, the built module's ModuleBase would be: `output/MyModuleName/2.0.1/` + + .PARAMETER ProjectName + The project name. Defaults to the empty string. + + .PARAMETER SourcePath + The path to the source folder name. Defaults to the empty string. + + .PARAMETER DocOutputFolder + The path to the where the documentation is written. Defaults to + the folder `./output/WikiContent`. + + .PARAMETER BuildInfo + The build info object from ModuleBuilder. Defaults to an empty hashtable. + + .NOTES + This is a build task that is primarily meant to be run by Invoke-Build but + wrapped by the Sampler project's build.ps1 (https://github.com/gaelcolas/Sampler). +#> +param +( + [Parameter()] + [System.String] + $ProjectPath = (property ProjectPath $BuildRoot), + + [Parameter()] + [System.String] + $OutputDirectory = (property OutputDirectory (Join-Path $BuildRoot 'output')), + + [Parameter()] + [System.String] + $BuiltModuleSubdirectory = (property BuiltModuleSubdirectory ''), + + [Parameter()] + [System.Management.Automation.SwitchParameter] + $VersionedOutputDirectory = (property VersionedOutputDirectory $true), + + [Parameter()] + [System.String] + $ProjectName = (property ProjectName ''), + + [Parameter()] + [System.String] + $SourcePath = (property SourcePath ''), + + [Parameter()] + [System.String] + $DocOutputFolder = (property DocOutputFolder 'WikiContent'), + + [Parameter()] + [System.Collections.Hashtable] + $BuildInfo = (property BuildInfo @{ }) +) + +# Synopsis: Clean the markdown metdata block from the markdown documentation. +Task Clean_Markdown_Metadata { + # Get the values for task variables, see https://github.com/gaelcolas/Sampler#task-variables. + . Set-SamplerTaskVariable + + $DocOutputFolder = Get-SamplerAbsolutePath -Path $DocOutputFolder -RelativeTo $OutputDirectory + + "`tDocs output folder path = '$DocOutputFolder'" + "" + + # cSpell: disable-next-line + $markdownFiles = Get-ChildItem -Path "$DocOutputFolder/*.md" -Exclude @('[Hh]ome.md', '_[Ss]idebar.md', '_[Ff]ooter.md') + + Write-Build -Color 'Magenta' -Text 'Cleaning the command documentation.' + + foreach ($markdownFile in $markdownFiles) + { + $markdownFile | Remove-MarkdownMetadataBlock -Force + } +} diff --git a/source/tasks/Clean_Markdown_Of_Public_Commands.build.ps1 b/source/tasks/Clean_Markdown_Of_Public_Commands.build.ps1 index 0f9cdc8..cc6a36b 100644 --- a/source/tasks/Clean_Markdown_Of_Public_Commands.build.ps1 +++ b/source/tasks/Clean_Markdown_Of_Public_Commands.build.ps1 @@ -79,12 +79,13 @@ Task Clean_Markdown_Of_Public_Commands { # Get the values for task variables, see https://github.com/gaelcolas/Sampler#task-variables. . Set-SamplerTaskVariable + $DocOutputFolder = Get-SamplerAbsolutePath -Path $DocOutputFolder -RelativeTo $OutputDirectory + "`tDocs output folder path = '$DocOutputFolder'" "" - $DocOutputFolder = Get-SamplerAbsolutePath -Path $DocOutputFolder -RelativeTo $OutputDirectory - - $markdownFiles = Get-ChildItem -Path "$DocOutputFolder/*.md" -Exclude '[Hh]ome.md' + # cSpell: disable-next-line + $markdownFiles = Get-ChildItem -Path "$DocOutputFolder/*.md" -Exclude @('[Hh]ome.md', '_[Ss]idebar.md', '_[Ff]ooter.md') Write-Build -Color 'Magenta' -Text 'Cleaning the command documentation.' diff --git a/source/tasks/Generate_Conceptual_Help.build.ps1 b/source/tasks/Generate_Conceptual_Help.build.ps1 index 3b91cb3..67f6a07 100644 --- a/source/tasks/Generate_Conceptual_Help.build.ps1 +++ b/source/tasks/Generate_Conceptual_Help.build.ps1 @@ -77,7 +77,7 @@ param $BuildInfo = (property BuildInfo @{ }) ) -# Synopsis: This task generates conceptual help for DSC resources. +# Synopsis: Generate conceptual help for DSC resources. task Generate_Conceptual_Help { # Get the values for task variables, see https://github.com/gaelcolas/Sampler#task-variables. . Set-SamplerTaskVariable diff --git a/source/tasks/Generate_External_Help_File_For_Public_Commands.build.ps1 b/source/tasks/Generate_External_Help_File_For_Public_Commands.build.ps1 index 7106086..4455829 100644 --- a/source/tasks/Generate_External_Help_File_For_Public_Commands.build.ps1 +++ b/source/tasks/Generate_External_Help_File_For_Public_Commands.build.ps1 @@ -80,7 +80,7 @@ param $BuildInfo = (property BuildInfo @{ }) ) -# Synopsis: Generate help file for the public commands from the built module. +# Synopsis: Generate external help file for the public commands from the built module. Task Generate_External_Help_File_For_Public_Commands { if (-not (Get-Module -Name 'PlatyPS' -ListAvailable)) { diff --git a/source/tasks/Generate_Markdown_For_Public_Commands.build.ps1 b/source/tasks/Generate_Markdown_For_Public_Commands.build.ps1 index 8ef6a69..781d560 100644 --- a/source/tasks/Generate_Markdown_For_Public_Commands.build.ps1 +++ b/source/tasks/Generate_Markdown_For_Public_Commands.build.ps1 @@ -178,7 +178,8 @@ Import-Module -Name '$ProjectName' -ErrorAction Stop Locale = '$HelpCultureInfo' HelpVersion = '$helpVersion' MetaData = @{ - Type = 'Command' + Type = 'Command' + Category = 'Commands' } Force = `$true ErrorAction = 'Stop' diff --git a/source/tasks/Generate_Wiki_Content.build.ps1 b/source/tasks/Generate_Wiki_Content.build.ps1 index 6b8e84d..0999361 100644 --- a/source/tasks/Generate_Wiki_Content.build.ps1 +++ b/source/tasks/Generate_Wiki_Content.build.ps1 @@ -80,7 +80,7 @@ param $BuildInfo = (property BuildInfo @{ }) ) -# Synopsis: This task generates wiki documentation for the DSC resources. +# Synopsis: Generate wiki documentation for the DSC resources. task Generate_Wiki_Content { # Get the values for task variables, see https://github.com/gaelcolas/Sampler#task-variables. . Set-SamplerTaskVariable @@ -105,7 +105,9 @@ task Generate_Wiki_Content { Write-Build -Color 'Magenta' -Text 'Generating Wiki content for all DSC resources based on source and built module.' - New-DscResourceWikiPage -SourcePath $SourcePath -BuiltModulePath $builtModuleBase -OutputPath $wikiOutputPath -Force + $dscResourceMarkdownMetadata = $BuildInfo.'DscResource.DocGenerator'.Generate_Wiki_Content + + New-DscResourceWikiPage -SourcePath $SourcePath -BuiltModulePath $builtModuleBase -OutputPath $wikiOutputPath -Metadata $dscResourceMarkdownMetadata -Force if ($wikiSourceExist) { diff --git a/source/tasks/Generate_Wiki_Sidebar.build.ps1 b/source/tasks/Generate_Wiki_Sidebar.build.ps1 new file mode 100644 index 0000000..6f1bd10 --- /dev/null +++ b/source/tasks/Generate_Wiki_Sidebar.build.ps1 @@ -0,0 +1,126 @@ +<# + .SYNOPSIS + This is a build task that generates GitHub Wiki Sidebar file based on + markdown metadata in existing markdown files. + + .PARAMETER ProjectPath + The root path to the project. Defaults to $BuildRoot. + + .PARAMETER OutputDirectory + The base directory of all output. Defaults to folder 'output' relative to + the $BuildRoot. + + .PARAMETER BuiltModuleSubdirectory + Sub folder where you want to build the Module to (instead of $OutputDirectory/$ModuleName). + This is especially useful when you want to build DSC Resources, but you don't want the + `Get-DscResource` command to find several instances of the same DSC Resources because + of the overlapping $Env:PSmodulePath (`$buildRoot/output` for the built module and `$buildRoot/output/RequiredModules`). + + In most cases I would recommend against setting $BuiltModuleSubdirectory. + + .PARAMETER VersionedOutputDirectory + Whether the Module is built with its versioned Subdirectory, as you would see it on a System. + For instance, if VersionedOutputDirectory is $true, the built module's ModuleBase would be: `output/MyModuleName/2.0.1/` + + .PARAMETER ProjectName + The project name. Defaults to the empty string. + + .PARAMETER SourcePath + The path to the source folder name. Defaults to the empty string. + + .PARAMETER DocOutputFolder + The path to the where the markdown documentation is written. Defaults to the + folder `./output/WikiContent`. + + .PARAMETER DebugTask + Whether to run the task in debug mode. Defaults to `$false`. + + .PARAMETER BuildInfo + The build info object from ModuleBuilder. Defaults to an empty hashtable. + + .NOTES + This is a build task that is primarily meant to be run by Invoke-Build but + wrapped by the Sampler project's build.ps1 (https://github.com/gaelcolas/Sampler). +#> +param +( + [Parameter()] + [System.String] + $ProjectPath = (property ProjectPath $BuildRoot), + + [Parameter()] + [System.String] + $OutputDirectory = (property OutputDirectory (Join-Path $BuildRoot 'output')), + + [Parameter()] + [System.String] + $BuiltModuleSubdirectory = (property BuiltModuleSubdirectory ''), + + [Parameter()] + [System.Management.Automation.SwitchParameter] + $VersionedOutputDirectory = (property VersionedOutputDirectory $true), + + [Parameter()] + [System.String] + $ProjectName = (property ProjectName ''), + + [Parameter()] + [System.String] + $SourcePath = (property SourcePath ''), + + [Parameter()] + [System.String] + $DocOutputFolder = (property DocOutputFolder 'WikiContent'), + + [Parameter()] + [System.Management.Automation.SwitchParameter] + $DebugTask = (property DebugTask $false), + + [Parameter()] + [System.Collections.Hashtable] + $BuildInfo = (property BuildInfo @{ }) +) + +# Synopsis: Generate GitHub Wiki sidebar based on existing markdown files. +task Generate_Wiki_Sidebar { + if (-not $script:PSBoundParameters.ContainsKey('DebugTask')) + { + $DebugTask = [System.Management.Automation.SwitchParameter] $BuildInfo.'DscResource.DocGenerator'.Generate_Wiki_Sidebar.Debug + } + + <# + Only show debug information if Debug was set to 'true' in build configuration + or if it was passed as a parameter. + #> + if ($DebugTask.IsPresent) + { + $local:VerbosePreference = 'Continue' + $local:DebugPreference = 'Continue' + + Write-Debug -Message 'Running task with debug information.' + } + + $alwaysOverwrite = [System.Boolean] $BuildInfo.'DscResource.DocGenerator'.Generate_Wiki_Sidebar.AlwaysOverwrite + + # Get the values for task variables, see https://github.com/gaelcolas/Sampler#task-variables. + . Set-SamplerTaskVariable + + $DocOutputFolder = Get-SamplerAbsolutePath -Path $DocOutputFolder -RelativeTo $OutputDirectory + + "`tDocs output folder path = '$DocOutputFolder'" + "" + + $newGitHubWikiSidebarParameters = @{ + DocumentationPath = $DocOutputFolder + ReplaceExisting = $alwaysOverwrite + Force = $true + } + + if ($DebugTask.IsPresent) + { + $newGitHubWikiSidebarParameters.Verbose = $true + $newGitHubWikiSidebarParameters.Debug = $true + } + + New-GitHubWikiSidebar @newGitHubWikiSidebarParameters +} diff --git a/source/tasks/Publish_GitHub_Wiki_Content.build.ps1 b/source/tasks/Publish_GitHub_Wiki_Content.build.ps1 index 0ccf028..8dc3dc3 100644 --- a/source/tasks/Publish_GitHub_Wiki_Content.build.ps1 +++ b/source/tasks/Publish_GitHub_Wiki_Content.build.ps1 @@ -95,7 +95,7 @@ param $BuildInfo = (property BuildInfo @{ }) ) -# Synopsis: This task publishes documentation to a GitHub Wiki repository. +# Synopsis: Publish documentation to a GitHub Wiki repository. task Publish_GitHub_Wiki_Content { if ([System.String]::IsNullOrEmpty($GitHubToken)) { diff --git a/tests/unit/private/New-DscClassResourceWikiPage.Tests.ps1 b/tests/unit/private/New-DscClassResourceWikiPage.Tests.ps1 index 86b5c04..d797e21 100644 --- a/tests/unit/private/New-DscClassResourceWikiPage.Tests.ps1 +++ b/tests/unit/private/New-DscClassResourceWikiPage.Tests.ps1 @@ -861,6 +861,108 @@ Resource description. -Exactly -Times 1 -Scope Context } } + + Context 'When adding metadata to the markdown file' { + BeforeAll { + # The class DSC resource in the built module. + $mockBuiltModuleScript = @' +[DscResource()] +class AzDevOpsProject +{ + [AzDevOpsProject] Get() + { + return [AzDevOpsProject] $this + } + + [System.Boolean] Test() + { + return $true + } + + [void] Set() {} + + [DscProperty(Key)] + [System.String]$ProjectName +} +'@ + # Uses Microsoft.PowerShell.Utility\Out-File to override the stub that is needed for the mocks. + $mockBuiltModuleScript | Microsoft.PowerShell.Utility\Out-File -FilePath "$mockBuiltModulePath\MyClassModule.psm1" -Encoding ascii -Force + + # The source file of class DSC resource. + $mockSourceScript = @' +<# + .SYNOPSIS + A DSC Resource for Azure DevOps that + represents the Project resource. + + This is another row. +#> +[DscResource()] +class AzDevOpsProject +{ + [AzDevOpsProject] Get() + { + return [AzDevOpsProject] $this + } + + [System.Boolean] Test() + { + return $true + } + + [void] Set() {} + + [DscProperty(Key)] + [System.String]$ProjectName +} +'@ + # Uses Microsoft.PowerShell.Utility\Out-File to override the stub that is needed for the mocks. + $mockSourceScript | Microsoft.PowerShell.Utility\Out-File -FilePath "$mockSourcePath\Classes\010.AzDevOpsProject.ps1" -Encoding ascii -Force + + $mockExpectedFileOutput = @' +--- +Module: MyClassModule +Type: ClassResource +--- + +# AzDevOpsProject + +## Parameters + +| Parameter | Attribute | DataType | Description | Allowed Values | +| --- | --- | --- | --- | --- | +| **ProjectName** | Key | System.String | | | + +## Description +'@ -replace '\r?\n', "`r`n" + + $mockNewDscResourcePowerShellHelpParameters = @{ + SourcePath = $mockSourcePath + BuiltModulePath = $mockBuiltModulePath + OutputPath = $TestDrive + Verbose = $true + Metadata = @{ + Type = 'ClassResource' + Module = 'MyClassModule' + } + } + + Mock -CommandName Out-File + } + + It 'Should not throw an exception' { + { + New-DscClassResourceWikiPage @mockNewDscResourcePowerShellHelpParameters + } | Should -Not -Throw + } + + It 'Should produce the correct output' { + Assert-MockCalled ` + -CommandName Out-File ` + -ParameterFilter $script:outFileContent_ParameterFilter ` + -Exactly -Times 1 -Scope Context + } + } } } } diff --git a/tests/unit/private/New-DscCompositeResourceWikiPage.Tests.ps1 b/tests/unit/private/New-DscCompositeResourceWikiPage.Tests.ps1 index 1bfd449..f7e1c8d 100644 --- a/tests/unit/private/New-DscCompositeResourceWikiPage.Tests.ps1 +++ b/tests/unit/private/New-DscCompositeResourceWikiPage.Tests.ps1 @@ -909,6 +909,219 @@ Configuration Example -Exactly -Times 0 } } + + Context 'When adding metadata to the markdown file' { + BeforeAll { + <# + This is the mocked embedded schema that is to be returned + together with the resource schema (which is mocked above) + for the mocked function Get-CompositeSchemaObject. + #> + $script:mockEmbeddedSchemaObject = @{ + ClassName = 'DSC_EmbeddedInstance' + ClassVersion = '1.0.0' + FriendlyName = 'EmbeddedInstance' + Attributes = @( + @{ + State = 'Key' + DataType = 'String' + ValueMap = @() + IsArray = $false + Name = 'EmbeddedId' + Description = 'Id Description' + EmbeddedInstance = '' + }, + @{ + State = 'Write' + DataType = 'String' + ValueMap = @( 'Value1', 'Value2', 'Value3' ) + IsArray = $false + Name = 'EmbeddedEnum' + Description = 'Enum Description.' + EmbeddedInstance = '' + }, + @{ + State = 'Required' + DataType = 'Uint32' + ValueMap = @() + IsArray = $false + Name = 'EmbeddedInt' + Description = 'Int Description.' + EmbeddedInstance = '' + }, + @{ + State = 'Read' + DataType = 'String' + ValueMap = @() + IsArray = $false + Name = 'EmbeddedRead' + Description = 'Read Description.' + EmbeddedInstance = '' + } + ) + } + + $mockWikiContentOutput = '--- +Module: MyClassModule +Type: CompositeResource +--- + +# MyCompositeResource + +## Parameters + +| Parameter | Attribute | DataType | Description | Allowed Values | +| --- | --- | --- | --- | --- | +| **StringProperty** | Required | String | Any description | | +| **StringValueMapProperty** | Required | String | Any description | `Value1`, `Value2` | +| **StringArrayWriteProperty** | Write | String[] | Any description | | + +## Description + +The description of the resource. +Second row of description. + +## Examples + +.EXAMPLE 1 + +Example description. + +Configuration Example +{ + Import-DSCResource -ModuleName MyModule + Node localhost + { + MyCompositeResource Something + { + Id = ''MyId'' + Enum = ''Value1'' + Int = 1 + } + } +} +' -replace '\r?\n', "`r`n" + + Mock ` + -CommandName Get-ChildItem ` + -ParameterFilter $script:getChildItemSchema_parameterFilter ` + -MockWith { $script:mockSchemaFiles } + + Mock ` + -CommandName Get-CompositeSchemaObject ` + -ParameterFilter $script:getCompositeSchemaObjectSchema_parameterFilter ` + -MockWith { + return @( + $script:mockGetCompositeSchemaObject + $script:mockEmbeddedSchemaObject + ) + } + + Mock ` + -CommandName Get-ChildItem ` + -ParameterFilter $script:getChildItemDescription_parameterFilter ` + -MockWith { + return @( + @{ + Name = 'README.MD' + FullName = $script:mockReadmePath + } + ) + } + + Mock ` + -CommandName Get-Content ` + -ParameterFilter $script:getContentReadme_parameterFilter ` + -MockWith { $script:mockGetContentReadme } + + Mock ` + -CommandName Get-ChildItem ` + -ParameterFilter $script:getChildItemExample_parameterFilter ` + -MockWith { $script:mockExampleFiles } + + Mock ` + -CommandName Get-DscResourceWikiExampleContent ` + -ParameterFilter $script:getDscResourceWikiExampleContent_parameterFilter ` + -MockWith { $script:mockExampleContent } + + Mock ` + -CommandName Out-File + + Mock ` + -CommandName Write-Warning ` + -ParameterFilter $script:writeWarningExample_parameterFilter + + Mock ` + -CommandName Write-Warning ` + -ParameterFilter $script:writeWarningDescription_parameterFilter + } + + It 'Should not throw an exception' { + { + New-DscCompositeResourceWikiPage @script:newDscResourceWikiPageOutput_parameters -Metadata @{ + Type = 'CompositeResource' + Module = 'MyClassModule' + } + } | Should -Not -Throw + } + + It 'Should produce the correct output' { + Assert-MockCalled ` + -CommandName Out-File ` + -ParameterFilter { + if ($InputObject -ne $mockWikiContentOutput) + { + # Helper to output the diff. + Out-Diff -Expected $mockWikiContentOutput -Actual $InputObject + } + + $InputObject -eq $mockWikiContentOutput + } ` + -Exactly -Times 1 + } + + It 'Should call the expected mocks ' { + Assert-MockCalled ` + -CommandName Get-ChildItem ` + -ParameterFilter $script:getChildItemSchema_parameterFilter ` + -Exactly -Times 1 + + Assert-MockCalled ` + -CommandName Get-CompositeSchemaObject ` + -ParameterFilter $script:getCompositeSchemaObjectSchema_parameterFilter ` + -Exactly -Times 1 + + Assert-MockCalled ` + -CommandName Get-ChildItem ` + -ParameterFilter $script:getChildItemDescription_parameterFilter ` + -Exactly -Times 1 + + Assert-MockCalled ` + -CommandName Get-Content ` + -ParameterFilter $script:getContentReadme_parameterFilter ` + -Exactly -Times 1 + + Assert-MockCalled ` + -CommandName Get-ChildItem ` + -ParameterFilter $script:getChildItemExample_parameterFilter ` + -Exactly -Times 1 + + Assert-MockCalled ` + -CommandName Get-DscResourceWikiExampleContent ` + -ParameterFilter $script:getDscResourceWikiExampleContent_parameterFilter ` + -Exactly -Times 1 + + Assert-MockCalled ` + -CommandName Write-Warning ` + -ParameterFilter $script:writeWarningExample_parameterFilter ` + -Exactly -Times 0 + + Assert-MockCalled ` + -CommandName Write-Warning ` + -ParameterFilter $script:writeWarningDescription_parameterFilter ` + -Exactly -Times 0 + } + } } } } diff --git a/tests/unit/private/New-DscMofResourceWikiPage.Tests.ps1 b/tests/unit/private/New-DscMofResourceWikiPage.Tests.ps1 index dfa17d8..a4de672 100644 --- a/tests/unit/private/New-DscMofResourceWikiPage.Tests.ps1 +++ b/tests/unit/private/New-DscMofResourceWikiPage.Tests.ps1 @@ -933,6 +933,231 @@ Configuration Example -Exactly -Times 0 } } + + Context 'When adding metadata to the markdown file' { + BeforeAll { + <# + This is the mocked embedded schema that is to be returned + together with the resource schema (which is mocked above) + for the mocked function Get-MofSchemaObject. + #> + $script:mockEmbeddedSchemaObject = @{ + ClassName = 'DSC_EmbeddedInstance' + ClassVersion = '1.0.0' + FriendlyName = 'EmbeddedInstance' + Attributes = @( + @{ + State = 'Key' + DataType = 'String' + ValueMap = @() + IsArray = $false + Name = 'EmbeddedId' + Description = 'Id Description' + EmbeddedInstance = '' + }, + @{ + State = 'Write' + DataType = 'String' + ValueMap = @( 'Value1', 'Value2', 'Value3' ) + IsArray = $false + Name = 'EmbeddedEnum' + Description = 'Enum Description.' + EmbeddedInstance = '' + }, + @{ + State = 'Required' + DataType = 'Uint32' + ValueMap = @() + IsArray = $false + Name = 'EmbeddedInt' + Description = 'Int Description.' + EmbeddedInstance = '' + }, + @{ + State = 'Read' + DataType = 'String' + ValueMap = @() + IsArray = $false + Name = 'EmbeddedRead' + Description = 'Read Description.' + EmbeddedInstance = '' + } + ) + } + + $mockWikiContentOutput = '--- +Module: MyClassModule +Type: MofResource +--- + +# MyResource + +## Parameters + +| Parameter | Attribute | DataType | Description | Allowed Values | +| --- | --- | --- | --- | --- | +| **Id** | Key | String | Id Description | | +| **Enum** | Write | String | Enum Description. | `Value1`, `Value2`, `Value3` | +| **Int** | Required | Uint32 | Int Description. | | +| **Read** | Read | String | Read Description. | | + +### DSC_EmbeddedInstance + +#### Parameters + +| Parameter | Attribute | DataType | Description | Allowed Values | +| --- | --- | --- | --- | --- | +| **EmbeddedId** | Key | String | Id Description | | +| **EmbeddedEnum** | Write | String | Enum Description. | `Value1`, `Value2`, `Value3` | +| **EmbeddedInt** | Required | Uint32 | Int Description. | | +| **EmbeddedRead** | Read | String | Read Description. | | + +## Description + +The description of the resource. +Second row of description. + +## Examples + +.EXAMPLE 1 + +Example description. + +Configuration Example +{ + Import-DSCResource -ModuleName MyModule + Node localhost + { + MyResource Something + { + Id = ''MyId'' + Enum = ''Value1'' + Int = 1 + } + } +} +' -replace '\r?\n', "`r`n" + + Mock ` + -CommandName Get-ChildItem ` + -ParameterFilter $script:getChildItemSchema_parameterFilter ` + -MockWith { $script:mockSchemaFiles } + + Mock ` + -CommandName Get-MofSchemaObject ` + -ParameterFilter $script:getMofSchemaObjectSchema_parameterFilter ` + -MockWith { + return @( + $script:mockGetMofSchemaObject + $script:mockEmbeddedSchemaObject + ) + } + + Mock ` + -CommandName Get-ChildItem ` + -ParameterFilter $script:getChildItemDescription_parameterFilter ` + -MockWith { + return @( + @{ + Name = 'README.MD' + FullName = $script:mockReadmePath + } + ) + } + + Mock ` + -CommandName Get-Content ` + -ParameterFilter $script:getContentReadme_parameterFilter ` + -MockWith { $script:mockGetContentReadme } + + Mock ` + -CommandName Get-ChildItem ` + -ParameterFilter $script:getChildItemExample_parameterFilter ` + -MockWith { $script:mockExampleFiles } + + Mock ` + -CommandName Get-DscResourceWikiExampleContent ` + -ParameterFilter $script:getDscResourceWikiExampleContent_parameterFilter ` + -MockWith { $script:mockExampleContent } + + Mock ` + -CommandName Out-File + + Mock ` + -CommandName Write-Warning ` + -ParameterFilter $script:writeWarningExample_parameterFilter + + Mock ` + -CommandName Write-Warning ` + -ParameterFilter $script:writeWarningDescription_parameterFilter + } + + It 'Should not throw an exception' { + { + New-DscMofResourceWikiPage @script:newDscResourceWikiPageOutput_parameters -Metadata @{ + Type = 'MofResource' + Module = 'MyClassModule' + } + } | Should -Not -Throw + } + + It 'Should produce the correct output' { + Assert-MockCalled ` + -CommandName Out-File ` + -ParameterFilter { + if ($InputObject -ne $mockWikiContentOutput) + { + # Helper to output the diff. + Out-Diff -Expected $mockWikiContentOutput -Actual $InputObject + } + + $InputObject -eq $mockWikiContentOutput + } ` + -Exactly -Times 1 + } + + It 'Should call the expected mocks ' { + Assert-MockCalled ` + -CommandName Get-ChildItem ` + -ParameterFilter $script:getChildItemSchema_parameterFilter ` + -Exactly -Times 1 + + Assert-MockCalled ` + -CommandName Get-MofSchemaObject ` + -ParameterFilter $script:getMofSchemaObjectSchema_parameterFilter ` + -Exactly -Times 1 + + Assert-MockCalled ` + -CommandName Get-ChildItem ` + -ParameterFilter $script:getChildItemDescription_parameterFilter ` + -Exactly -Times 1 + + Assert-MockCalled ` + -CommandName Get-Content ` + -ParameterFilter $script:getContentReadme_parameterFilter ` + -Exactly -Times 1 + + Assert-MockCalled ` + -CommandName Get-ChildItem ` + -ParameterFilter $script:getChildItemExample_parameterFilter ` + -Exactly -Times 1 + + Assert-MockCalled ` + -CommandName Get-DscResourceWikiExampleContent ` + -ParameterFilter $script:getDscResourceWikiExampleContent_parameterFilter ` + -Exactly -Times 1 + + Assert-MockCalled ` + -CommandName Write-Warning ` + -ParameterFilter $script:writeWarningExample_parameterFilter ` + -Exactly -Times 0 + + Assert-MockCalled ` + -CommandName Write-Warning ` + -ParameterFilter $script:writeWarningDescription_parameterFilter ` + -Exactly -Times 0 + } + } } } } diff --git a/tests/unit/private/Remove-MarkdownMetadataBlock.Tests.ps1 b/tests/unit/private/Remove-MarkdownMetadataBlock.Tests.ps1 deleted file mode 100644 index 580ff7c..0000000 --- a/tests/unit/private/Remove-MarkdownMetadataBlock.Tests.ps1 +++ /dev/null @@ -1,93 +0,0 @@ -#region HEADER -$script:projectPath = "$PSScriptRoot\..\..\.." | Convert-Path -$script:projectName = (Get-ChildItem -Path "$script:projectPath\*\*.psd1" | Where-Object -FilterScript { - ($_.Directory.Name -match 'source|src' -or $_.Directory.Name -eq $_.BaseName) -and - $(try - { - Test-ModuleManifest -Path $_.FullName -ErrorAction Stop - } - catch - { - $false - }) - }).BaseName - -$script:moduleName = Get-Module -Name $script:projectName -ListAvailable | Select-Object -First 1 -Remove-Module -Name $script:moduleName -Force -ErrorAction 'SilentlyContinue' - -Import-Module $script:moduleName -Force -ErrorAction 'Stop' -#endregion HEADER - -InModuleScope $script:moduleName { - Describe 'Remove-MarkdownMetadataBlock' { - It 'Should remove metadata from a markdown file' { - $testFilePath = Join-Path -Path $TestDrive -ChildPath 'TestFile.md' - - Set-Content -Path $testFilePath -Value @" ---- -title: Test Title ---- -# Test Content -"@ - - Remove-MarkdownMetadataBlock -FilePath $testFilePath - - $content = Get-Content -Path $testFilePath -Raw - - $content | Should -Not -Match '---' - $content | Should -Match '# Test Content' - } - - It 'Should not throw when file does not contain metadata' { - $testFilePathNoMetadata = Join-Path $TestDrive -ChildPath 'TestFileNoMetadata.md' - Set-Content -Path $testFilePathNoMetadata -Value @" -# Test Content -"@ - - { Remove-MarkdownMetadataBlock -FilePath $testFilePathNoMetadata } | Should -Not -Throw - - $content = Get-Content -Path $testFilePathNoMetadata -Raw - - $content | Should -Match '# Test Content' - } - - It 'Should throw when file does not exist' { - $nonExistentFilePath = Join-Path $TestDrive -ChildPath 'NonExistentFile.md' - - { Remove-MarkdownMetadataBlock -FilePath $nonExistentFilePath } | Should -Throw - } - - It 'Should not modify the file if there is no metadata' { - $testFilePathNoMetadata = Join-Path $TestDrive -ChildPath 'TestFileNoMetadata.md' - - Set-Content -Path $testFilePathNoMetadata -Value @" -# Test Content -"@ - $originalContent = Get-Content -Path $testFilePathNoMetadata -Raw - - Remove-MarkdownMetadataBlock -FilePath $testFilePathNoMetadata - - $newContent = Get-Content -Path $testFilePathNoMetadata -Raw - - $newContent | Should -BeExactly $originalContent - } - - It 'Should not have line endings at the top of the file' { - $testFilePath = Join-Path -Path $TestDrive -ChildPath 'TestFile.md' - - Set-Content -Path $testFilePath -Value @" ---- -title: Test Title ---- -# Test Content -"@ - - Remove-MarkdownMetadataBlock -FilePath $testFilePath - - $content = Get-Content -Path $testFilePath -Raw - - $content[0] | Should -Not -Be "`r" - $content[0] | Should -Not -Be "`n" - } - } -} diff --git a/tests/unit/public/New-GitHubWikiSidebar.Tests.ps1 b/tests/unit/public/New-GitHubWikiSidebar.Tests.ps1 new file mode 100644 index 0000000..f39c5a0 --- /dev/null +++ b/tests/unit/public/New-GitHubWikiSidebar.Tests.ps1 @@ -0,0 +1,171 @@ +#region HEADER +$script:projectPath = "$PSScriptRoot\..\..\.." | Convert-Path +$script:projectName = (Get-ChildItem -Path "$script:projectPath\*\*.psd1" | Where-Object -FilterScript { + ($_.Directory.Name -match 'source|src' -or $_.Directory.Name -eq $_.BaseName) -and + $(try + { + Test-ModuleManifest -Path $_.FullName -ErrorAction Stop + } + catch + { + $false + }) + }).BaseName + +$script:moduleName = Get-Module -Name $script:projectName -ListAvailable | Select-Object -First 1 +Remove-Module -Name $script:moduleName -Force -ErrorAction 'SilentlyContinue' + +Import-Module $script:moduleName -Force -ErrorAction 'Stop' +#endregion HEADER + +Describe 'New-GitHubWikiSidebar' { + BeforeAll { + $documentationPath = "$($TestDrive.FullName)/WikiContent" + $outputFilePath = $documentationPath | Join-Path -ChildPath 'CustomSidebar.md' + + New-Item -Path $documentationPath -ItemType 'Directory' -Force | Out-Null + + Set-Content -Path "$($TestDrive.FullName)/WikiContent/Home.md" -Value @' +# MockModule +'@ + + Set-Content -Path "$($TestDrive.FullName)/WikiContent/MockMarkdownWithoutMetadata.md" -Value @' +# Some topic +'@ + + Set-Content -Path "$($TestDrive.FullName)/WikiContent/RandomHelpTopic.md" -Value @' +--- +Category: Help topics +--- + +# RandomHelpTopic +'@ + + Set-Content -Path "$($TestDrive.FullName)/WikiContent/Get-Something.md" -Value @' +--- +Type: Command +Category: Commands +--- + +# Get-Something +'@ + + Set-Content -Path "$($TestDrive.FullName)/WikiContent/MockResource.md" -Value @' +--- +Type: MofResource +Category: Resources +--- + +# MockResource +'@ + + Mock -CommandName Out-File -ModuleName $script:moduleName + Mock -CommandName Write-Information -ModuleName $script:moduleName + } + + Context 'When provided with valid inputs' { + Context 'When using parameter Force' { + BeforeAll { + $script:mockWikiContentOutput = @' +[Home](Home) + +### General + +- [MockMarkdownWithoutMetadata](MockMarkdownWithoutMetadata) + +### Commands + +- [Get-Something](Get-Something) + +### Help topics + +- [RandomHelpTopic](RandomHelpTopic) + +### Resources + +- [MockResource](MockResource) +'@ -replace '\r?\n', "`r`n" + + } + It 'Should not throw any exceptions and call Out-File with correct parameters' { + { + # + New-GitHubWikiSidebar -DocumentationPath $documentationPath -SidebarFileName 'CustomSidebar.md' -Force + } | Should -Not -Throw + + Assert-MockCalled -CommandName Out-File -ParameterFilter { + # This is used to output the diff if the output is not as expected. + if ($InputObject -ne $script:mockWikiContentOutput) + { + # Helper to output the diff. + Out-Diff -Expected $script:mockWikiContentOutput -Actual $InputObject + } + + $FilePath -eq $outputFilePath -and $Force -eq $false -and ` + $InputObject -eq $script:mockWikiContentOutput + } -Exactly -Times 1 -Scope It -ModuleName $script:moduleName + } + } + + Context 'When using parameter Confirm set to $false' { + It 'Should call Out-File with Force parameter set to true' { + New-GitHubWikiSidebar -DocumentationPath $documentationPath -SidebarFileName 'CustomSidebar.md' -Confirm:$false + + Assert-MockCalled -CommandName Out-File -ParameterFilter { + $FilePath -eq $outputFilePath -and $Force -eq $false + } -Exactly -Times 1 -Scope It -ModuleName $script:moduleName + } + } + + Context 'When ReplaceExisting parameter is used' { + It 'Should call Out-File with Force parameter set to true' { + New-GitHubWikiSidebar -DocumentationPath $documentationPath -SidebarFileName 'CustomSidebar.md' -ReplaceExisting -Force + + Assert-MockCalled -CommandName Out-File -ParameterFilter { + $FilePath -eq $outputFilePath -and $Force -eq $true + } -Exactly -Times 1 -Scope It -ModuleName $script:moduleName + } + } + } + + It 'Should not call Out-File when using parameter WhatIf' { + New-GitHubWikiSidebar -DocumentationPath $documentationPath -SidebarFileName 'CustomSidebar.md' -ReplaceExisting -WhatIf + + Assert-MockCalled -CommandName Out-File -Exactly -Times 0 -Scope It -ModuleName $script:moduleName + } + + Context 'When provided with invalid inputs' { + It 'Should throw an exception if DocumentationPath does not exist' { + { + New-GitHubWikiSidebar -DocumentationPath './nonexistent/path' -OutputPath './output' -SidebarFileName 'CustomSidebar.md' + } | Should -Throw + } + + It 'Should throw an exception if OutputPath does not exist' { + { + New-GitHubWikiSidebar -DocumentationPath $documentationPath -OutputPath './nonexistent/path' -SidebarFileName 'CustomSidebar.md' + } | Should -Throw + } + } + + Context 'When sidebar filename already exist' { + BeforeAll { + Mock -CommandName Out-File -ModuleName $script:moduleName + Mock -CommandName Write-Warning -ModuleName $script:moduleName + Mock -CommandName Test-Path -ModuleName $script:moduleName -ParameterFilter { + $Path -match 'CustomSidebar.md' + } -MockWith { + return $true + } + } + + It 'Should write a warning message and not call Out-File' { + { + New-GitHubWikiSidebar -DocumentationPath $documentationPath -OutputPath $documentationPath -SidebarFileName 'CustomSidebar.md' + } | Should -Not -Throw + + Assert-MockCalled -CommandName Out-File -Exactly -Times 0 -Scope It -ModuleName $script:moduleName + Assert-MockCalled -CommandName Write-Warning -Exactly -Times 1 -Scope It -ModuleName $script:moduleName + } + } +} diff --git a/tests/unit/public/Remove-MarkdownMetadataBlock.Tests.ps1 b/tests/unit/public/Remove-MarkdownMetadataBlock.Tests.ps1 new file mode 100644 index 0000000..5664b65 --- /dev/null +++ b/tests/unit/public/Remove-MarkdownMetadataBlock.Tests.ps1 @@ -0,0 +1,140 @@ +#region HEADER +$script:projectPath = "$PSScriptRoot\..\..\.." | Convert-Path +$script:projectName = (Get-ChildItem -Path "$script:projectPath\*\*.psd1" | Where-Object -FilterScript { + ($_.Directory.Name -match 'source|src' -or $_.Directory.Name -eq $_.BaseName) -and + $(try + { + Test-ModuleManifest -Path $_.FullName -ErrorAction Stop + } + catch + { + $false + }) + }).BaseName + +$script:moduleName = Get-Module -Name $script:projectName -ListAvailable | Select-Object -First 1 +Remove-Module -Name $script:moduleName -Force -ErrorAction 'SilentlyContinue' + +Import-Module $script:moduleName -Force -ErrorAction 'Stop' +#endregion HEADER + +Describe 'Remove-MarkdownMetadataBlock' { + It 'Should remove metadata from a markdown file when -Force is used' { + $testFilePath = Join-Path -Path $TestDrive -ChildPath 'TestFile.md' + + Set-Content -Path $testFilePath -Value @" +--- +title: Test Title +--- +# Test Content +"@ + + Remove-MarkdownMetadataBlock -FilePath $testFilePath -Force + + $content = Get-Content -Path $testFilePath -Raw + + $content | Should -Not -Match '---' + $content | Should -Match '# Test Content' + } + + It 'Should remove metadata from a markdown file when -Confirm:$false is used' { + $testFilePath = Join-Path -Path $TestDrive -ChildPath 'TestFile.md' + + Set-Content -Path $testFilePath -Value @" +--- +title: Test Title +--- +# Test Content +"@ + + Remove-MarkdownMetadataBlock -FilePath $testFilePath -Confirm:$false + + $content = Get-Content -Path $testFilePath -Raw + + $content | Should -Not -Match '---' + $content | Should -Match '# Test Content' + } + + It 'Should not remove metadata from a markdown file when -WhatIf is used' { + $testFilePath = Join-Path -Path $TestDrive -ChildPath 'TestFileNoForce.md' + + Set-Content -Path $testFilePath -Value @" +--- +title: Test Title +--- +# Test Content +"@ + + Remove-MarkdownMetadataBlock -FilePath $testFilePath -WhatIf + + $content = Get-Content -Path $testFilePath -Raw + + $content | Should -Match '---' + $content | Should -Match '# Test Content' + } + + It 'Should not throw when file does not contain metadata' { + $testFilePathNoMetadata = Join-Path $TestDrive -ChildPath 'TestFileNoMetadata.md' + + Set-Content -Path $testFilePathNoMetadata -Value @" +# Test Content +"@ + + { Remove-MarkdownMetadataBlock -FilePath $testFilePathNoMetadata -Confirm:$false } | Should -Not -Throw + + $content = Get-Content -Path $testFilePathNoMetadata -Raw + + $content | Should -Match '# Test Content' + } + + It 'Should throw when file does not exist' { + $nonExistentFilePath = Join-Path $TestDrive -ChildPath 'NonExistentFile.md' + + { Remove-MarkdownMetadataBlock -FilePath $nonExistentFilePath -Confirm:$false } | Should -Throw + } + + It 'Should not modify the file if there is no metadata' { + $testFilePathNoMetadata = Join-Path $TestDrive -ChildPath 'TestFileNoMetadata.md' + + Set-Content -Path $testFilePathNoMetadata -Value @" +# Test Content +"@ + $originalContent = Get-Content -Path $testFilePathNoMetadata -Raw + + Remove-MarkdownMetadataBlock -FilePath $testFilePathNoMetadata -Confirm:$false + + $newContent = Get-Content -Path $testFilePathNoMetadata -Raw + + $newContent | Should -BeExactly $originalContent + } + + It 'Should not have line endings at the top of the file' { + $testFilePath = Join-Path -Path $TestDrive -ChildPath 'TestFile.md' + + Set-Content -Path $testFilePath -Value @" +--- +title: Test Title +--- +# Test Content +"@ + + Remove-MarkdownMetadataBlock -FilePath $testFilePath -Confirm:$false + + $content = Get-Content -Path $testFilePath -Raw + + $content[0] | Should -Not -Be "`r" + $content[0] | Should -Not -Be "`n" + } + + It 'Should throw when provided with a non-leaf path' { + $nonLeafPath = Join-Path -Path $TestDrive -ChildPath 'NonLeafPath' + + { Remove-MarkdownMetadataBlock -FilePath $nonLeafPath -Confirm:$false } | Should -Throw + } + + It 'Should throw when provided with a non-existent path' { + $nonExistentPath = Join-Path -Path $TestDrive -ChildPath 'NonExistentPath.md' + + { Remove-MarkdownMetadataBlock -FilePath $nonExistentPath -Confirm:$false } | Should -Throw + } +} diff --git a/tests/unit/tasks/Clean_Markdown_Metadata.Tests.ps1 b/tests/unit/tasks/Clean_Markdown_Metadata.Tests.ps1 new file mode 100644 index 0000000..12e8ad8 --- /dev/null +++ b/tests/unit/tasks/Clean_Markdown_Metadata.Tests.ps1 @@ -0,0 +1,75 @@ +#region HEADER +$script:projectPath = "$PSScriptRoot\..\..\.." | Convert-Path +$script:projectName = (Get-ChildItem -Path "$script:projectPath\*\*.psd1" | Where-Object -FilterScript { + ($_.Directory.Name -match 'source|src' -or $_.Directory.Name -eq $_.BaseName) -and + $(try + { + Test-ModuleManifest -Path $_.FullName -ErrorAction Stop + } + catch + { + $false + }) + }).BaseName + +$script:moduleName = Get-Module -Name $script:projectName -ListAvailable | Select-Object -First 1 +Remove-Module -Name $script:moduleName -Force -ErrorAction 'SilentlyContinue' + +Import-Module $script:moduleName -Force -ErrorAction 'Stop' +#endregion HEADER + +Describe 'Clean_Markdown_Metadata' { + BeforeAll { + Mock -Command Get-BuiltModuleVersion -MockWith { + return [PSCustomObject]@{ + Version = '1.0.0-preview1' + PreReleaseString = 'preview1' + ModuleVersion = '1.0.0' + } + } + + Mock -CommandName Get-SamplerBuiltModuleManifest + Mock -CommandName Get-SamplerBuiltModuleBase -MockWith { + return $TestDrive.FullName + } + + Mock -CommandName Remove-MarkdownMetadataBlock + + New-Item -Path "$($TestDrive.FullName)/WikiContent" -ItemType 'Directory' -Force | Out-Null + + Set-Content -Path "$($TestDrive.FullName)/WikiContent/Get-Something.md" -Value 'Mock markdown file 1' + + Set-Content -Path "$($TestDrive.FullName)/WikiContent/home.md" -Value 'Mock markdown file 1' + } + + It 'Should export the build script alias' { + $buildTaskName = 'Clean_Markdown_Metadata' + $buildScriptAliasName = 'Task.{0}' -f $buildTaskName + + $script:buildScript = Get-Command -Name $buildScriptAliasName -Module $script:projectName + + $script:buildScript.Name | Should -Be $buildScriptAliasName + $script:buildScript.ReferencedCommand | Should -Be ('{0}.build.ps1' -f $buildTaskName) + } + + It 'Should reference an existing build script' { + Test-Path -Path $script:buildScript.Definition | Should -BeTrue + } +5 + It 'Should run the build task without throwing' { + { + $taskParameters = @{ + ProjectName = 'MockModule' + ProjectPath = $TestDrive.FullName + OutputDirectory = $TestDrive.FullName + # Using the markdown created when the project was built. + DocOutputFolder = $TestDrive.FullName | Join-Path -ChildPath 'WikiContent' + SourcePath = "$($TestDrive.FullName)/source" + } + + Invoke-Build -Task $buildTaskName -File $script:buildScript.Definition @taskParameters + } | Should -Not -Throw + + Assert-MockCalled -CommandName Remove-MarkdownMetadataBlock -Exactly -Times 1 -Scope 'It' + } +} diff --git a/tests/unit/tasks/Generate_Wiki_Sidebar.Tests.ps1 b/tests/unit/tasks/Generate_Wiki_Sidebar.Tests.ps1 new file mode 100644 index 0000000..f739089 --- /dev/null +++ b/tests/unit/tasks/Generate_Wiki_Sidebar.Tests.ps1 @@ -0,0 +1,106 @@ +#region HEADER +$script:projectPath = "$PSScriptRoot\..\..\.." | Convert-Path +$script:projectName = (Get-ChildItem -Path "$script:projectPath\*\*.psd1" | Where-Object -FilterScript { + ($_.Directory.Name -match 'source|src' -or $_.Directory.Name -eq $_.BaseName) -and + $(try + { + Test-ModuleManifest -Path $_.FullName -ErrorAction Stop + } + catch + { + $false + }) + }).BaseName + +$script:moduleName = Get-Module -Name $script:projectName -ListAvailable | Select-Object -First 1 +Remove-Module -Name $script:moduleName -Force -ErrorAction 'SilentlyContinue' + +Import-Module $script:moduleName -Force -ErrorAction 'Stop' +#endregion HEADER + +Describe 'Generate_Wiki_Sidebar' { + BeforeAll { + Mock -Command Get-BuiltModuleVersion -MockWith { + return [PSCustomObject]@{ + Version = '1.0.0-preview1' + PreReleaseString = 'preview1' + ModuleVersion = '1.0.0' + } + } + + Mock -CommandName Get-SamplerBuiltModuleManifest + Mock -CommandName Get-SamplerBuiltModuleBase -MockWith { + return $TestDrive.FullName + } + + Mock -CommandName New-GitHubWikiSidebar + + New-Item -Path "$($TestDrive.FullName)/WikiContent" -ItemType 'Directory' -Force | Out-Null + + Set-Content -Path "$($TestDrive.FullName)/WikiContent/Get-Something.md" -Value 'Mock markdown file 1' + + Set-Content -Path "$($TestDrive.FullName)/WikiContent/home.md" -Value 'Mock markdown file 1' + } + + It 'Should export the build script alias' { + $buildTaskName = 'Generate_Wiki_Sidebar' + $buildScriptAliasName = 'Task.{0}' -f $buildTaskName + + $script:buildScript = Get-Command -Name $buildScriptAliasName -Module $script:projectName + + $script:buildScript.Name | Should -Be $buildScriptAliasName + $script:buildScript.ReferencedCommand | Should -Be ('{0}.build.ps1' -f $buildTaskName) + } + + It 'Should reference an existing build script' { + Test-Path -Path $script:buildScript.Definition | Should -BeTrue + } +5 + It 'Should run the build task without throwing' { + { + $taskParameters = @{ + ProjectName = 'MockModule' + ProjectPath = $TestDrive.FullName + OutputDirectory = $TestDrive.FullName + # Using the markdown created when the project was built. + DocOutputFolder = $TestDrive.FullName | Join-Path -ChildPath 'WikiContent' + SourcePath = "$($TestDrive.FullName)/source" + } + + Invoke-Build -Task $buildTaskName -File $script:buildScript.Definition @taskParameters + } | Should -Not -Throw + + Assert-MockCalled -CommandName New-GitHubWikiSidebar -ParameterFilter { + Write-Verbose ($VerbosePreference | Out-String) -Verbose + Write-Verbose ($DebugPreference | Out-String) -Verbose + $true -eq $true + } -Exactly -Times 1 -Scope 'It' + } + + It 'Should run the build task in debug mode without throwing' { + { + $taskParameters = @{ + ProjectName = 'MockModule' + ProjectPath = $TestDrive.FullName + OutputDirectory = $TestDrive.FullName + # Using the markdown created when the project was built. + DocOutputFolder = $TestDrive.FullName | Join-Path -ChildPath 'WikiContent' + SourcePath = "$($TestDrive.FullName)/source" + DebugTask = $true + } + + Invoke-Build -Task $buildTaskName -File $script:buildScript.Definition @taskParameters + } | Should -Not -Throw + + Assert-MockCalled -CommandName New-GitHubWikiSidebar -ParameterFilter { + Write-Verbose ($VerbosePreference | Out-String) -Verbose + Write-Verbose ($DebugPreference | Out-String) -Verbose + $VerbosePreference -eq 'Continue' -and + <# + Must negate from SilentlyContinu because DebugPreference is set to different + values on Windows PowerShell (Continue) and PowerShell (Inquire). + #> + $DebugPreference -ne 'SilentlyContinue' + } -Exactly -Times 1 -Scope 'It' + } +}