diff --git a/CHANGELOG.md b/CHANGELOG.md index 78fa432..5c20229 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,12 +19,9 @@ and this project adheres to [Semantic Versioning](http://semver.org/). yourself if you build help. `$PSBPreference.Docs.AlphabeticParamsOrder` is removed, because PlatyPS 1.x always sorts parameters alphabetically and offers no way back. Generated markdown carries the 1.x schema, though its - on-disk layout is unchanged. `Build-PSBuildUpdatableHelp` warns and returns - until the cabinet pipeline migrates in - [#152](https://github.com/psake/PowerShellBuild/issues/152); it could never - succeed in 0.8.x either, see - [#169](https://github.com/psake/PowerShellBuild/issues/169). See the - [v0.8 → v1.0 migration guide](docs/migration-v0.8-to-v1.0.md). + on-disk layout is unchanged, and now includes a module landing page that + 0.14.x never produced. Updatable help is covered separately under **Fixed**. + See the [v0.8 → v1.0 migration guide](docs/migration-v0.8-to-v1.0.md). - [**#144**](https://github.com/psake/PowerShellBuild/issues/144) **Breaking:** `Test-PSBuildScriptAnalysis` now counts PSScriptAnalyzer @@ -56,6 +53,18 @@ and this project adheres to [Semantic Versioning](http://semver.org/). ### Fixed +- [**#169**](https://github.com/psake/PowerShellBuild/issues/169) + `Build-PSBuildUpdatableHelp` produces a help cabinet. It never could before: + it needed a module landing page that `Build-PSBuildMarkdown` did not + generate, passed an undefined variable as the cabinet source folder, and was + never given the module name — so any build that reached the + `GenerateUpdatableHelp` task failed with a parameter-binding error. + `Build-PSBuildMarkdown` now writes the landing page, and the task passes the + module name and output path it always should have. Using the task requires a + `HelpInfoUri` in your module manifest; without one it warns and produces + nothing, rather than writing a cabinet with no `HelpInfo.xml` to find it by. + See the [v0.8 → v1.0 migration guide](docs/migration-v0.8-to-v1.0.md). + - [**#147**](https://github.com/psake/PowerShellBuild/issues/147) `Test-PSBuildScriptAnalysis` retries the analysis when a PSScriptAnalyzer rule crashes on an internal race diff --git a/PowerShellBuild/IB.tasks.ps1 b/PowerShellBuild/IB.tasks.ps1 index fe50828..969e3d0 100644 --- a/PowerShellBuild/IB.tasks.ps1 +++ b/PowerShellBuild/IB.tasks.ps1 @@ -155,7 +155,13 @@ $genUpdatableHelpPreReqs = { # Synopsis: Create updatable help .cab file based on PlatyPS markdown help Task GenerateUpdatableHelp -if (. $genUpdatableHelpPreReqs) BuildHelp, { - Build-PSBuildUpdatableHelp -DocsPath $PSBPreference.Docs.RootDir -OutputPath $PSBPreference.Help.UpdatableHelpOutDir + $buildUpdatableHelpParameters = @{ + DocsPath = $PSBPreference.Docs.RootDir + OutputPath = $PSBPreference.Help.UpdatableHelpOutDir + ModulePath = $PSBPreference.Build.ModuleOutDir + Module = $PSBPreference.General.ModuleName + } + Build-PSBuildUpdatableHelp @buildUpdatableHelpParameters } # Synopsis: Publish module to the defined PowerShell repository diff --git a/PowerShellBuild/Public/Build-PSBuildMarkdown.ps1 b/PowerShellBuild/Public/Build-PSBuildMarkdown.ps1 index 0f44807..24fc461 100644 --- a/PowerShellBuild/Public/Build-PSBuildMarkdown.ps1 +++ b/PowerShellBuild/Public/Build-PSBuildMarkdown.ps1 @@ -95,6 +95,10 @@ function Build-PSBuildMarkdown { ModuleInfo = $moduleInfo OutputFolder = $stagingPath Locale = $Locale + # The landing page is what carries the module GUID, locale, and help + # version into the updatable-help cabinet. 0.14.x never produced one, + # which is why Build-PSBuildUpdatableHelp could not work. + WithModulePage = $true # PlatyPS 1.x defaults this front matter key to "-Help.xml", where # 0.14.x wrote "-help.xml". The key is what Export-MamlCommandHelp # names the MAML file after, so pinning it here keeps the markdown, the MAML @@ -124,10 +128,19 @@ function Build-PSBuildMarkdown { ) foreach ($markdownFile in $generatedMarkdown) { $destinationPath = Join-Path -Path $localePath -ChildPath $markdownFile.Name - if ((Test-Path -LiteralPath $destinationPath) -and -not $Overwrite) { - # Already refreshed above; regenerating would discard hand-written prose. + $isModuleLandingPage = $markdownFile.BaseName -eq $ModuleName + + # The landing page is always replaced. It is generated content -- an index of + # the module's commands, plus the module GUID and locale that the cabinet step + # stamps its .cab name from -- so a copy left in place goes stale the moment a + # command is added or removed. PlatyPS has no refresh that would preserve an + # edited body either: Update-MarkdownModuleFile rewrites it wholesale. Command + # help is different, and is skipped here because it was already refreshed in + # place above, where hand-written prose survives. + if (-not $isModuleLandingPage -and (Test-Path -LiteralPath $destinationPath) -and -not $Overwrite) { continue } + Move-Item -LiteralPath $markdownFile.FullName -Destination $destinationPath -Force } } finally { diff --git a/PowerShellBuild/Public/Build-PSBuildUpdatableHelp.ps1 b/PowerShellBuild/Public/Build-PSBuildUpdatableHelp.ps1 index 986a22e..37e94af 100644 --- a/PowerShellBuild/Public/Build-PSBuildUpdatableHelp.ps1 +++ b/PowerShellBuild/Public/Build-PSBuildUpdatableHelp.ps1 @@ -5,25 +5,23 @@ function Build-PSBuildUpdatableHelp { .DESCRIPTION Create updatable help .cab file based on PlatyPS markdown help. - Not implemented against PlatyPS 1.x yet. The cabinet pipeline is migrated in - psake/PowerShellBuild#152 along with the three defects in #169 that prevented this - function from ever succeeding. Until then it reports that updatable help was skipped - and returns without writing anything. + Requires the module manifest to declare a HelpInfoUri. That URI is where Update-Help + looks for the help content, so a cabinet built without one cannot be consumed. .PARAMETER DocsPath - Path to PlatyPS markdown help files. + Path to PlatyPS markdown help files. Must contain a locale directory holding the + module landing page, /.md. .PARAMETER OutputPath Path to create updatable help .cab file in. + .PARAMETER ModulePath + Path to the built module. The MAML written by Build-PSBuildMAMLHelp is read from + /, and the manifest is read from /.psd1. .PARAMETER Module - Name of the module to create a .cab file for. Defaults to the - $ModuleName variable from the parent scope. + Name of the module to create a .cab file for. .EXAMPLE - PS> Build-PSBuildUpdatableHelp -DocsPath ./docs -OutputPath ./Output/UpdatableHelp + PS> Build-PSBuildUpdatableHelp -DocsPath ./docs -OutputPath ./Output/UpdatableHelp -ModulePath ./Output/MyModule/1.0.0 -Module MyModule - Reports that updatable help is not available and returns. + Create help .cab file based on PlatyPS markdown help. #> - # The parameters are unused only because the body is stubbed. They stay so the public - # signature does not change twice -- once here and again when #152 restores the body. - [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSReviewUnusedParameter', '')] [CmdletBinding()] param( [parameter(Mandatory)] @@ -32,14 +30,87 @@ function Build-PSBuildUpdatableHelp { [parameter(Mandatory)] [string]$OutputPath, - [string]$Module = $ModuleName + [parameter(Mandatory)] + [string]$ModulePath, + + [parameter(Mandatory)] + [string]$Module ) - # Deliberately references no PlatyPS command. Naming New-ExternalHelpCab here is enough to - # make PowerShell autoload platyPS 0.14.2 on any session that resolves it, and once that - # module is loaded, Microsoft.PowerShell.PlatyPS can no longer be imported in the same - # process -- both ship their own YamlDotNet with different assembly identities. Leaving the - # old call in place would poison every session that still has 0.14.2 installed, which is - # every consumer part-way through the upgrade. - Write-Warning $LocalizedData.UpdatableHelpNotMigrated + if ($null -ne $IsWindows -and -not $IsWindows) { + Write-Warning $LocalizedData.MakeCabNotAvailable + return + } + + # Update-Help resolves help content through the manifest's HelpInfoUri. Without one, + # New-HelpCabinetFile still writes the cabinet and its zip but fails before writing the + # HelpInfo.xml that makes them findable, leaving output that looks complete and is not. + # Refusing up front is better than producing that. + $manifestPath = [IO.Path]::Combine($ModulePath, "$Module.psd1") + if (-not (Test-Path -LiteralPath $manifestPath)) { + Write-Warning ($LocalizedData.UnableToFindModuleManifest -f $manifestPath) + return + } + $helpInfoUri = (Import-PowerShellDataFile -Path $manifestPath).HelpInfoUri + if ([string]::IsNullOrWhiteSpace($helpInfoUri)) { + Write-Warning ($LocalizedData.HelpInfoUriRequired -f $Module) + return + } + + # Work out what can actually be built before touching OutputPath. Clearing it first and + # then skipping every locale would delete a previous build's cabinet and replace it with + # nothing, which is the same half-produced outcome the guards above exist to prevent. + $cabinetWork = foreach ($locale in (Get-ChildItem -Path $DocsPath -Directory).Name) { + # The landing page is a module document rather than command help. It is what carries + # the module GUID and locale into the cabinet, and it is generated by + # Build-PSBuildMarkdown; a docs tree from an older build will not have one. + $markdownModuleFile = [IO.Path]::Combine($DocsPath, $locale, "$Module.md") + if (-not (Test-Path -LiteralPath $markdownModuleFile)) { + Write-Warning ($LocalizedData.ModuleLandingPageNotFound -f $markdownModuleFile, $locale) + continue + } + + $cabinetFilesFolder = [IO.Path]::Combine($ModulePath, $locale) + if (-not (Test-Path -LiteralPath $cabinetFilesFolder)) { + Write-Warning ($LocalizedData.FolderDoesNotExist -f $cabinetFilesFolder) + continue + } + + [PSCustomObject]@{ + CabinetFilesFolder = $cabinetFilesFolder + MarkdownModuleFile = $markdownModuleFile + } + } + + if (-not $cabinetWork) { + return + } + + # Create updatable help output directory + if (-not (Test-Path -LiteralPath $OutputPath)) { + $newItemSplat = @{ + ItemType = 'Directory' + Verbose = ($VerbosePreference -eq 'Continue') + Path = $OutputPath + } + New-Item @newItemSplat > $null + } else { + Write-Verbose ($LocalizedData.DirectoryAlreadyExists -f $OutputPath) + $removeItemSplat = @{ + Recurse = $true + Force = $true + Verbose = ($VerbosePreference -eq 'Continue') + } + Get-ChildItem $OutputPath | Remove-Item @removeItemSplat + } + + foreach ($work in $cabinetWork) { + $cabinetParameters = @{ + CabinetFilesFolder = $work.CabinetFilesFolder + MarkdownModuleFile = $work.MarkdownModuleFile + OutputFolder = $OutputPath + Verbose = ($VerbosePreference -eq 'Continue') + } + New-HelpCabinetFile @cabinetParameters > $null + } } diff --git a/PowerShellBuild/en-US/Messages.psd1 b/PowerShellBuild/en-US/Messages.psd1 index fae187d..48c8516 100644 --- a/PowerShellBuild/en-US/Messages.psd1 +++ b/PowerShellBuild/en-US/Messages.psd1 @@ -3,7 +3,8 @@ NoCommandsExported=No commands have been exported. Skipping markdown generation. FailedToGenerateMarkdownHelp=Failed to generate markdown help. : {0} AddingFileToPsm1=Adding [{0}] to PSM1 MakeCabNotAvailable=MakeCab.exe is not available. Cannot create help cab. -UpdatableHelpNotMigrated=Updatable help was skipped. The cabinet pipeline has not been migrated to Microsoft.PowerShell.PlatyPS 1.x yet; see psake/PowerShellBuild#152. +HelpInfoUriRequired=Updatable help was skipped for [{0}]. The module manifest does not declare a HelpInfoUri, which is where Update-Help looks for the help content, so a cabinet built without one cannot be used. +ModuleLandingPageNotFound=Updatable help was skipped for locale [{1}]. The module landing page [{0}] does not exist. It is generated by the GenerateMarkdown task; regenerate the documentation and try again. DirectoryAlreadyExists=Directory already exists [{0}]. PathLongerThan3Chars=Path [{0}] must be longer than 3 characters. BuildSystemDetails=Build System Details: diff --git a/PowerShellBuild/psakeFile.ps1 b/PowerShellBuild/psakeFile.ps1 index 2208e18..daef4ce 100644 --- a/PowerShellBuild/psakeFile.ps1 +++ b/PowerShellBuild/psakeFile.ps1 @@ -215,7 +215,14 @@ $genUpdatableHelpPreReqs = { $result } Task GenerateUpdatableHelp -Depends $PSBGenerateUpdatableHelpDependency -PreCondition $genUpdatableHelpPreReqs { - Build-PSBuildUpdatableHelp -DocsPath $PSBPreference.Docs.RootDir -OutputPath $PSBPreference.Help.UpdatableHelpOutDir -Verbose:($VerbosePreference -eq 'Continue') + $buildUpdatableHelpParameters = @{ + DocsPath = $PSBPreference.Docs.RootDir + OutputPath = $PSBPreference.Help.UpdatableHelpOutDir + ModulePath = $PSBPreference.Build.ModuleOutDir + Module = $PSBPreference.General.ModuleName + Verbose = ($VerbosePreference -eq 'Continue') + } + Build-PSBuildUpdatableHelp @buildUpdatableHelpParameters } -Description 'Create updatable help .cab file based on PlatyPS markdown help' Task Publish -Depends $PSBPublishDependency { diff --git a/docs/migration-v0.8-to-v1.0.md b/docs/migration-v0.8-to-v1.0.md index ee05ac1..6457270 100644 --- a/docs/migration-v0.8-to-v1.0.md +++ b/docs/migration-v0.8-to-v1.0.md @@ -34,8 +34,10 @@ One line per break; follow the link for details and migration steps. — PlatyPS 1.x always sorts alphabetically, so the setting could no longer do anything. - [Generated markdown uses the PlatyPS 1.x schema](#generated-markdown-uses-the-platyps-1x-schema) — expect a large diff in `docs/` on the first 1.0.0 build. -- [Updatable help is temporarily unavailable](#updatable-help-is-temporarily-unavailable) - — the cabinet pipeline is migrated before 1.0.0 ships. +- [Updatable help works, and now requires a `HelpInfoUri`](#updatable-help-works-and-now-requires-a-helpinfouri) + — it could never succeed in 0.8.x; using it now needs a `HelpInfoUri` in your manifest. +- [Your `docs/` tree gains a module landing page](#your-docs-tree-gains-a-module-landing-page) + — a new `.md` appears alongside the per-command documents. > More entries will follow as the remaining Phase 2 work lands. @@ -307,24 +309,74 @@ this should be schema churn rather than content loss — but verify. Consumer guidance for converting a committed tree is [#154](https://github.com/psake/PowerShellBuild/issues/154). -### Updatable help is temporarily unavailable +### Updatable help works, and now requires a `HelpInfoUri` -`Build-PSBuildUpdatableHelp` and the `GenerateUpdatableHelp` task write a -warning and return without producing a cabinet. The 1.x cabinet pipeline -is migrated in [#152](https://github.com/psake/PowerShellBuild/issues/152) -before 1.0.0 ships. +`Build-PSBuildUpdatableHelp` and the `GenerateUpdatableHelp` task produce a +help cabinet, its `.zip`, and a `HelpInfo.xml`. In 0.8.x they could not: +the function needed a module landing page that `Build-PSBuildMarkdown` +never generated, passed an undefined variable as the cabinet source +folder, and never received the module name — three separate defects, +recorded in [#169](https://github.com/psake/PowerShellBuild/issues/169). +Any 0.8.x build that reached this task failed with a parameter-binding +error, so nothing that worked before stops working. -This costs nothing in practice: the function could never succeed in -0.8.x either. It required a module landing page that -`Build-PSBuildMarkdown` never generated, and it passed an undefined -variable as the cabinet source folder — three separate defects, recorded -in [#169](https://github.com/psake/PowerShellBuild/issues/169). The task -is opt-in and is not part of the default build, so most consumers never -reached it. +**Your module manifest must declare a `HelpInfoUri`.** That URI is where +`Update-Help` looks for the content, so a cabinet built without one cannot +be consumed. If it is missing, the task now writes a warning and produces +nothing: -**Detection:** `Updatable help was skipped. The cabinet pipeline has not -been migrated to Microsoft.PowerShell.PlatyPS 1.x yet` in the build -output, where 0.8.x raised a parameter-binding error. +```text +Updatable help was skipped for [MyModule]. The module manifest does not +declare a HelpInfoUri, ... +``` + +Refusing is deliberate. `New-HelpCabinetFile` will otherwise write the +cabinet and its `.zip` and then fail before writing the `HelpInfo.xml` +that makes them findable — output that looks complete and is useless. + +**Migration:** add the URI where you publish help. + +```powershell +# In your module manifest +HelpInfoUri = 'https://example.com/mymodule/help' +``` + +Nothing is needed if you do not use the `GenerateUpdatableHelp` task; it +is opt-in and not part of the default build. + +**Calling the function directly?** Its signature changed. `Module` is now +mandatory rather than defaulting from a caller-scope variable, and +`ModulePath` is new — it is where the MAML written by the `GenerateMAML` +task is read from. + +**Before (0.8.x):** + +```powershell +Build-PSBuildUpdatableHelp -DocsPath ./docs -OutputPath ./Output/UpdatableHelp +``` + +**After (1.0.0):** + +```powershell +Build-PSBuildUpdatableHelp -DocsPath ./docs -OutputPath ./Output/UpdatableHelp ` + -ModulePath ./Output/MyModule/1.0.0 -Module MyModule +``` + +### Your `docs/` tree gains a module landing page + +`Build-PSBuildMarkdown` now generates `//.md` +alongside the per-command documents. 0.14.x never produced one. + +The page carries the module GUID, locale, and help version into the +updatable-help cabinet, which is why its absence was one of the three +defects above. It is also a different document type from command help, and +is excluded from MAML generation automatically — a module page in a MAML +export batch aborts the entire export +([PowerShell/platyPS#862](https://github.com/PowerShell/platyPS/issues/862)), +so `Build-PSBuildMAMLHelp` filters it out. + +**Detection:** a new `.md` file appears in your docs tree on the +first 1.0.0 build. If you commit `docs/`, commit it too. ## Adding an entry (for PR contributors) diff --git a/tests/Build-PSBuildHelp.tests.ps1 b/tests/Build-PSBuildHelp.tests.ps1 index 1d38a2b..614fde8 100644 --- a/tests/Build-PSBuildHelp.tests.ps1 +++ b/tests/Build-PSBuildHelp.tests.ps1 @@ -1,12 +1,15 @@ -# Baseline coverage for the three help-building functions (psake/PowerShellBuild#149). +# Unit coverage for the three help-building functions (psake/PowerShellBuild#149). # -# Build-PSBuildMarkdown, Build-PSBuildMAMLHelp, and Build-PSBuildUpdatableHelp have had no -# tests. The repository does not run its own docs tasks either -- the root psakeFile.ps1 goes -# Init -> Clean -> Build -> Analyze -> Pester -> Publish and never invokes GenerateMarkdown, -# GenerateMAML, or GenerateUpdatableHelp -- so nothing observes these functions today. That -# makes the PlatyPS 1.x migration (#105) a rewrite of three uncovered functions. This file is -# the red-before-green baseline they regress against, written against the CURRENT platyPS -# 0.14.2 behavior. +# These functions had no tests of their own before #149. They were not entirely unobserved, +# though: build.tests.ps1 builds tests/TestModule through -FromModule PowerShellBuild, whose +# Build task depends on BuildHelp, so GenerateMarkdown and GenerateMAML have been exercised +# end to end all along and "Has MAML help XML" pinned the output layout. What was missing was +# coverage of the functions directly, with specific options, and anything at all for +# GenerateUpdatableHelp -- which is not in the default Build chain. +# +# The file began as a red-before-green baseline against platyPS 0.14.2 and now asserts the +# PlatyPS 1.x behavior the migration produces. The task wiring, which no function-level test +# can reach, is covered in build.tests.ps1. # # Every invocation runs in a background job. That is not incidental: platyPS 0.14.2 and # Microsoft.PowerShell.PlatyPS 1.x each load their own YamlDotNet.dll through NestedModules, @@ -71,10 +74,9 @@ Describe 'Help building functions' -Skip:(-not $script:platyPSAvailable) { } } - It 'writes a module landing page named for the module' -Skip { - # Skipped: New-MarkdownHelp is called without -WithModulePage, so the landing page - # is never produced. That is defect 1 of psake/PowerShellBuild#169 and the reason - # Build-PSBuildUpdatableHelp cannot run at all. Unskip when #169 is fixed. + It 'writes a module landing page named for the module' { + # The cabinet step reads the module GUID, locale, and help version from this page. + # Its absence was defect 1 of psake/PowerShellBuild#169. $landingPageName = '{0}.md' -f $script:markdownScenario.ModuleName Join-Path -Path $script:markdownScenario.LocalePath -ChildPath $landingPageName | Should -Exist @@ -96,6 +98,38 @@ Describe 'Help building functions' -Skip:(-not $script:platyPSAvailable) { $markdown | Should -Not -Match 'schema:\s*2\.0\.0' } + It 'replaces the landing page on rebuild rather than leaving it stale' { + # The cabinet's .cab file name is stamped from this page's module GUID, so a copy + # left in place after the GUID changes names the cabinet for a module that no + # longer exists and Update-Help cannot match it. The page is also the command + # index, which goes stale as soon as a command is added or removed. Command help + # is deliberately not replaced this way -- that is the assertion below. + $landingPagePath = Join-Path -Path $script:markdownScenario.LocalePath -ChildPath ( + '{0}.md' -f $script:markdownScenario.ModuleName + ) + $originalGuid = ([regex]::Match( + (Get-Content -Path $landingPagePath -Raw), 'Module Guid:\s*(\S+)' + )).Groups[1].Value + $originalGuid | Should -Not -BeNullOrEmpty + + $manifestPath = Join-Path -Path $script:markdownScenario.ModulePath -ChildPath ( + '{0}.psd1' -f $script:markdownScenario.ModuleName + ) + $newGuid = [guid]::NewGuid().ToString() + (Get-Content -Path $manifestPath -Raw) -replace [regex]::Escape($originalGuid), $newGuid | + Set-Content -Path $manifestPath + + $rebuildJobParameter = @{ + ModulePath = $script:builtModulePath + CommandName = 'Build-PSBuildMarkdown' + Parameter = New-PSBuildMarkdownParameter -Scenario $script:markdownScenario + } + $rebuild = Invoke-PSBuildCommandInJob @rebuildJobParameter + $rebuild.Threw | Should -BeFalse + + Get-Content -Path $landingPagePath -Raw | Should -Match ([regex]::Escape($newGuid)) + } + It 'keeps the markdown directly under the locale directory' { # New-MarkdownCommandHelp writes to /. The function # flattens that back out, so a nested module directory here means the flattening @@ -143,34 +177,101 @@ Describe 'Help building functions' -Skip:(-not $script:platyPSAvailable) { } } - Context 'Build-PSBuildUpdatableHelp' { + Context 'Build-PSBuildUpdatableHelp' -Skip:(-not $script:onWindows) { BeforeAll { $script:cabScenario = New-PSBuildDocsScenario -Path $TestDrive -Name 'cab' + + $cabMarkdownJobParameter = @{ + ModulePath = $script:builtModulePath + CommandName = 'Build-PSBuildMarkdown' + Parameter = New-PSBuildMarkdownParameter -Scenario $script:cabScenario + } + $null = Invoke-PSBuildCommandInJob @cabMarkdownJobParameter + + # MAML has to land in the module directory, because that is where the cabinet step + # reads it from -- the same path the GenerateUpdatableHelp task supplies. + $cabMamlJobParameter = @{ + ModulePath = $script:builtModulePath + CommandName = 'Build-PSBuildMAMLHelp' + Parameter = @{ + Path = $script:cabScenario.DocsPath + DestinationPath = $script:cabScenario.ModulePath + } + } + $null = Invoke-PSBuildCommandInJob @cabMamlJobParameter + $cabJobParameter = @{ ModulePath = $script:builtModulePath CommandName = 'Build-PSBuildUpdatableHelp' Parameter = @{ DocsPath = $script:cabScenario.DocsPath OutputPath = $script:cabScenario.UpdatableHelpPath + ModulePath = $script:cabScenario.ModulePath Module = $script:cabScenario.ModuleName } } $script:cabResult = Invoke-PSBuildCommandInJob @cabJobParameter } - # Pins the documented intermediate state, not the destination. The cabinet pipeline - # migrates in #152 together with the three defects in #169 that stopped this function - # ever succeeding. Until then it must fail quietly rather than throw, and above all it - # must not name a platyPS 0.14.2 command: resolving one autoloads that module, and a - # session holding it can no longer import Microsoft.PowerShell.PlatyPS at all. - It 'returns without throwing' { - $script:cabResult.Threw | Should -BeFalse + It 'completes without error' { $script:cabResult.ErrorMessage | Should -BeNullOrEmpty + $script:cabResult.Threw | Should -BeFalse + } + + It 'produces a cabinet file' { + Get-ChildItem -Path $script:cabScenario.UpdatableHelpPath -Filter '*.cab' | + Should -Not -BeNullOrEmpty + } + + It 'produces the help info manifest' { + # Written only when the manifest declares a HelpInfoUri. Without one the cabinet is + # still produced and this file is not, which is output that looks complete and is + # useless to Update-Help. + Get-ChildItem -Path $script:cabScenario.UpdatableHelpPath -Filter '*HelpInfo.xml' | + Should -Not -BeNullOrEmpty + } + + It 'names the cabinet for the module, its GUID, and the locale' { + $manifestPath = Join-Path -Path $script:cabScenario.ModulePath -ChildPath ( + '{0}.psd1' -f $script:cabScenario.ModuleName + ) + $moduleGuid = (Import-PowerShellDataFile -Path $manifestPath).GUID + $expectedName = '{0}_{1}_{2}_HelpContent.cab' -f + $script:cabScenario.ModuleName, $moduleGuid, $script:cabScenario.Locale + + @(Get-ChildItem -Path $script:cabScenario.UpdatableHelpPath -Filter '*.cab')[0].Name | + Should -Be $expectedName } + } + + Context 'Build-PSBuildUpdatableHelp refuses to half-produce' -Skip:(-not $script:onWindows) { + + It 'declines when the manifest declares no HelpInfoUri' { + # New-HelpCabinetFile writes the cabinet and its zip, then fails before writing the + # HelpInfo.xml that Update-Help needs to find them. Stopping first is the whole + # point of the guard, so this asserts nothing at all was written. + $scenario = New-PSBuildDocsScenario -Path $TestDrive -Name 'nouri' + $manifestPath = Join-Path -Path $scenario.ModulePath -ChildPath ( + '{0}.psd1' -f $scenario.ModuleName + ) + (Get-Content -Path $manifestPath -Raw) -replace "(?m)^\s*HelpInfoUri.*$", '' | + Set-Content -Path $manifestPath + + $guardJobParameter = @{ + ModulePath = $script:builtModulePath + CommandName = 'Build-PSBuildUpdatableHelp' + Parameter = @{ + DocsPath = $scenario.DocsPath + OutputPath = $scenario.UpdatableHelpPath + ModulePath = $scenario.ModulePath + Module = $scenario.ModuleName + } + } + $result = Invoke-PSBuildCommandInJob @guardJobParameter - It 'writes nothing' { - $script:cabScenario.UpdatableHelpPath | Should -Not -Exist + $result.Threw | Should -BeFalse + $scenario.UpdatableHelpPath | Should -Not -Exist } } } diff --git a/tests/TestModule/TestModule/TestModule.psd1 b/tests/TestModule/TestModule/TestModule.psd1 index c93090b..4d2d09e 100644 --- a/tests/TestModule/TestModule/TestModule.psd1 +++ b/tests/TestModule/TestModule/TestModule.psd1 @@ -12,6 +12,11 @@ CmdletsToExport = @() VariablesToExport = @() AliasesToExport = @() + # Updatable help is resolved through this URI. It is a placeholder: the fixture + # is never published, but New-HelpCabinetFile refuses to write a HelpInfo.xml + # without one, so the cabinet tests need it present. + HelpInfoUri = 'https://example.com/testmodule/help' + PrivateData = @{ PSData = @{ # Tags = @() diff --git a/tests/build.tests.ps1 b/tests/build.tests.ps1 index ba218ae..d86d593 100644 --- a/tests/build.tests.ps1 +++ b/tests/build.tests.ps1 @@ -1,4 +1,10 @@ # spell-checker:ignore excludeme +BeforeDiscovery { + # Cabinet generation shells out to makecab.exe. $IsWindows does not exist on Windows + # PowerShell 5.1, so its absence also means Windows. + $script:onWindows = $IsWindows -or $null -eq $IsWindows +} + Describe 'Build' { BeforeAll { @@ -115,4 +121,43 @@ Describe 'Build' { "$script:testModuleOutputPath/en-US/TestModule-help.xml" | Should -Exist } } + + Context 'Updatable help task' -Skip:(-not $script:onWindows) { + + # Exercises the GenerateUpdatableHelp TASK, not just Build-PSBuildUpdatableHelp. + # psake/PowerShellBuild#169 defect 3 lived in the wiring rather than the function: the + # task passed neither the module name nor the module output path, so every + # function-level fix could pass while the task stayed broken. Only running the task + # catches that, and psake cannot nest, so it runs in a job like the builds above. + BeforeAll { + $script:updatableHelpOutputPath = if ($env:GITHUB_ACTION) { + [IO.Path]::Combine($env:BHProjectPath, 'Output', 'UpdatableHelp') + } else { + [IO.Path]::Combine($script:testModuleSource, 'Output', 'UpdatableHelp') + } + + # Not received, matching the contexts above -- psake writes its task banner and + # build report to the host, and receiving them here would interleave that with + # Pester's own output. + Start-Job -ScriptBlock { + Set-Location -Path $using:testModuleSource + ./build.ps1 -Task GenerateUpdatableHelp + } | Wait-Job > $null + } + + AfterAll { + Remove-Item $script:updatableHelpOutputPath -Recurse -Force -ErrorAction SilentlyContinue + Remove-Item $script:testModuleOutputPath -Recurse -Force -ErrorAction SilentlyContinue + } + + It 'Produces a help cabinet' { + Get-ChildItem -Path $script:updatableHelpOutputPath -Filter '*.cab' -ErrorAction SilentlyContinue | + Should -Not -BeNullOrEmpty + } + + It 'Produces the help info manifest' { + Get-ChildItem -Path $script:updatableHelpOutputPath -Filter '*HelpInfo.xml' -ErrorAction SilentlyContinue | + Should -Not -BeNullOrEmpty + } + } } diff --git a/tests/fixtures/PSBuildTestFixture/PSBuildTestFixture.psd1 b/tests/fixtures/PSBuildTestFixture/PSBuildTestFixture.psd1 index 587d7da..036cb15 100644 --- a/tests/fixtures/PSBuildTestFixture/PSBuildTestFixture.psd1 +++ b/tests/fixtures/PSBuildTestFixture/PSBuildTestFixture.psd1 @@ -14,6 +14,11 @@ CmdletsToExport = @() VariablesToExport = @() AliasesToExport = @() + # Updatable help is resolved through this URI. It is a placeholder: the fixture + # is never published, but New-HelpCabinetFile refuses to write a HelpInfo.xml + # without one, so the cabinet tests need it present. + HelpInfoUri = 'https://example.com/psbuildtestfixture/help' + PrivateData = @{ PSData = @{ Tags = @('PowerShellBuild', 'TestFixture')