mirror of https://github.com/raandree/NTFSSecurity
Browse Source
ci: move CI from AppVeyor to GitHub Actions and publish the docs to the wikipull/97/head
committed by
GitHub
18 changed files with 965 additions and 192 deletions
@ -0,0 +1,279 @@ |
|||||
|
<# |
||||
|
.SYNOPSIS |
||||
|
Converts the documentation in the Docs folder into the pages of the GitHub wiki. |
||||
|
|
||||
|
.DESCRIPTION |
||||
|
Writes a wiki page for every page in Docs except the contributor guide (Contributing.md and the Contributing |
||||
|
folder). Docs/README.md becomes the page Home, and every other page keeps its file name, so a page in |
||||
|
Docs/Cmdlets becomes a page named after its cmdlet. The platyPS metadata at the top of the cmdlet pages is |
||||
|
removed. Relative links point to the wiki pages; links to other files of the repository point to the files on |
||||
|
GitHub. Links in code stay unchanged. |
||||
|
|
||||
|
The script also writes the sidebar, which lists the cmdlets in the groups of the cmdlet list in Docs/README.md; |
||||
|
the footer; and the page How-to-install, which keeps the address of the former wiki page working. |
||||
|
|
||||
|
The script removes everything in DestinationPath except the .git folder, so that pages that no longer exist in |
||||
|
Docs disappear from the wiki. |
||||
|
|
||||
|
.PARAMETER Path |
||||
|
Specifies the Docs folder of the repository. |
||||
|
|
||||
|
.PARAMETER DestinationPath |
||||
|
Specifies the folder to write the wiki pages to, usually a clone of the wiki repository. The script creates the |
||||
|
folder if it doesn't exist. |
||||
|
|
||||
|
.PARAMETER RepositoryUrl |
||||
|
Specifies the address of the repository on GitHub, for links to files that aren't wiki pages. |
||||
|
|
||||
|
.PARAMETER Branch |
||||
|
Specifies the branch for links to files that aren't wiki pages. |
||||
|
|
||||
|
.EXAMPLE |
||||
|
git clone https://github.com/raandree/NTFSSecurity.wiki.git $env:TEMP\wiki |
||||
|
.\.github\scripts\Export-WikiContent.ps1 -Path .\Docs -DestinationPath $env:TEMP\wiki |
||||
|
git -C $env:TEMP\wiki status |
||||
|
|
||||
|
Writes the wiki pages into a clone of the wiki and shows which pages change. |
||||
|
#> |
||||
|
[CmdletBinding(SupportsShouldProcess)] |
||||
|
param ( |
||||
|
[Parameter(Mandatory)] |
||||
|
[ValidateScript({ Test-Path -LiteralPath $_ -PathType Container })] |
||||
|
[string] |
||||
|
$Path, |
||||
|
|
||||
|
[Parameter(Mandatory)] |
||||
|
[ValidateNotNullOrEmpty()] |
||||
|
[string] |
||||
|
$DestinationPath, |
||||
|
|
||||
|
[Parameter()] |
||||
|
[ValidateNotNullOrEmpty()] |
||||
|
[string] |
||||
|
$RepositoryUrl = 'https://github.com/raandree/NTFSSecurity', |
||||
|
|
||||
|
[Parameter()] |
||||
|
[ValidateNotNullOrEmpty()] |
||||
|
[string] |
||||
|
$Branch = 'master' |
||||
|
) |
||||
|
|
||||
|
$ErrorActionPreference = 'Stop' |
||||
|
|
||||
|
function Resolve-RepositoryPath { |
||||
|
<# |
||||
|
Returns the path of a link target relative to the repository root, with / as separator, or nothing if the |
||||
|
link leaves the repository. |
||||
|
#> |
||||
|
param ( |
||||
|
[Parameter(Mandatory)] |
||||
|
[string] |
||||
|
$Directory, |
||||
|
|
||||
|
[Parameter(Mandatory)] |
||||
|
[string] |
||||
|
$Link |
||||
|
) |
||||
|
|
||||
|
$segments = New-Object -TypeName 'System.Collections.Generic.List[string]' |
||||
|
foreach ($segment in (('{0}/{1}' -f $Directory, $Link) -split '/')) { |
||||
|
if ($segment -eq '..') { |
||||
|
if ($segments.Count -eq 0) { |
||||
|
return |
||||
|
} |
||||
|
$segments.RemoveAt($segments.Count - 1) |
||||
|
} elseif ($segment -and $segment -ne '.') { |
||||
|
$segments.Add($segment) |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
$segments -join '/' |
||||
|
} |
||||
|
|
||||
|
function ConvertTo-WikiLink { |
||||
|
<# |
||||
|
Returns the wiki address of a link on a page in Directory: the name of a wiki page, the address of a file of |
||||
|
the repository on GitHub, or the link itself if it is absolute or points to the same page. |
||||
|
#> |
||||
|
param ( |
||||
|
[Parameter(Mandatory)] |
||||
|
[string] |
||||
|
$Url, |
||||
|
|
||||
|
[Parameter(Mandatory)] |
||||
|
[string] |
||||
|
$Directory |
||||
|
) |
||||
|
|
||||
|
if ($Url -match '^(?:[a-zA-Z][a-zA-Z0-9+.-]*:|//|#)') { |
||||
|
return $Url |
||||
|
} |
||||
|
|
||||
|
$linkPath, $anchor = $Url -split '#', 2 |
||||
|
$target = Resolve-RepositoryPath -Directory $Directory -Link ([uri]::UnescapeDataString($linkPath)) |
||||
|
if (-not $target) { |
||||
|
return $Url |
||||
|
} |
||||
|
|
||||
|
$fragment = if ($anchor) { '#' + $anchor } else { '' } |
||||
|
if ($pageNames.ContainsKey($target)) { |
||||
|
return $pageNames[$target] + $fragment |
||||
|
} |
||||
|
|
||||
|
$view = if (Test-Path -LiteralPath (Join-Path -Path $repositoryRoot -ChildPath $target) -PathType Container) { |
||||
|
'tree' |
||||
|
} else { |
||||
|
'blob' |
||||
|
} |
||||
|
'{0}/{1}/{2}/{3}{4}' -f $RepositoryUrl.TrimEnd('/'), $view, $Branch, $target, $fragment |
||||
|
} |
||||
|
|
||||
|
function Convert-MarkdownLink { |
||||
|
<# |
||||
|
Returns the Markdown text with the links of a page in Directory converted to wiki addresses. Links in code |
||||
|
spans and fenced code blocks stay unchanged. |
||||
|
#> |
||||
|
[Diagnostics.CodeAnalysis.SuppressMessageAttribute( |
||||
|
'PSReviewUnusedParameter', 'Directory', Justification = 'The match evaluator script block uses it.' |
||||
|
)] |
||||
|
param ( |
||||
|
[Parameter(Mandatory)] |
||||
|
[AllowEmptyString()] |
||||
|
[string] |
||||
|
$Markdown, |
||||
|
|
||||
|
[Parameter(Mandatory)] |
||||
|
[string] |
||||
|
$Directory |
||||
|
) |
||||
|
|
||||
|
$inlinePattern = '(?<code>(?<ticks>`+).+?\k<ticks>)|(?<prefix>!?\[(?:[^\[\]`]|`[^`]*`)*\]\()(?<url>[^)\s]+)(?<suffix>(?:\s+"[^"]*")?\))' |
||||
|
$definitionPattern = '^(?<prefix>\s{0,3}\[[^\]]+\]:\s*)(?<url>\S+)(?<suffix>.*)$' |
||||
|
$convertLink = { |
||||
|
param ($match) |
||||
|
|
||||
|
if ($match.Groups['code'].Success) { |
||||
|
return $match.Value |
||||
|
} |
||||
|
$match.Groups['prefix'].Value + (ConvertTo-WikiLink -Url $match.Groups['url'].Value -Directory $Directory) + |
||||
|
$match.Groups['suffix'].Value |
||||
|
} |
||||
|
|
||||
|
$fence = $null |
||||
|
$lines = foreach ($line in ($Markdown -split '\r?\n')) { |
||||
|
if ($line -match '^\s{0,3}(?<fence>`{3,}|~{3,})') { |
||||
|
if (-not $fence) { |
||||
|
$fence = $Matches['fence'] |
||||
|
} elseif ($Matches['fence'].StartsWith($fence)) { |
||||
|
$fence = $null |
||||
|
} |
||||
|
$line |
||||
|
continue |
||||
|
} |
||||
|
if ($fence) { |
||||
|
$line |
||||
|
continue |
||||
|
} |
||||
|
|
||||
|
$line = [regex]::Replace($line, $inlinePattern, $convertLink) |
||||
|
[regex]::Replace($line, $definitionPattern, $convertLink) |
||||
|
} |
||||
|
|
||||
|
$lines -join "`n" |
||||
|
} |
||||
|
|
||||
|
$docsRoot = (Resolve-Path -LiteralPath $Path).ProviderPath.TrimEnd('\', '/') |
||||
|
$repositoryRoot = Split-Path -Path $docsRoot -Parent |
||||
|
$docsName = Split-Path -Path $docsRoot -Leaf |
||||
|
$destinationRoot = $ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath($DestinationPath).TrimEnd('\', '/') |
||||
|
|
||||
|
# Refuse a destination that contains the documentation, such as the repository itself. |
||||
|
$separator = [IO.Path]::DirectorySeparatorChar |
||||
|
if (($docsRoot + $separator).StartsWith($destinationRoot + $separator, [StringComparison]::OrdinalIgnoreCase)) { |
||||
|
throw "The destination '$destinationRoot' contains the documentation in '$docsRoot'. Specify a clone of the wiki." |
||||
|
} |
||||
|
|
||||
|
# Collect the pages: Docs/README.md is the home page, and the contributor guide stays in the repository. |
||||
|
$pageNames = @{} |
||||
|
$pages = foreach ($file in Get-ChildItem -LiteralPath $docsRoot -Filter '*.md' -File -Recurse) { |
||||
|
$repositoryPath = $file.FullName.Substring($repositoryRoot.Length + 1) -replace '\\', '/' |
||||
|
if ($repositoryPath -match ('^{0}/Contributing(?:\.md$|/)' -f [regex]::Escape($docsName))) { |
||||
|
continue |
||||
|
} |
||||
|
|
||||
|
$name = if ($repositoryPath -eq "$docsName/README.md") { 'Home' } else { $file.BaseName } |
||||
|
if ($pageNames.Values -contains $name) { |
||||
|
throw "Two pages in '$docsRoot' would become the wiki page '$name'." |
||||
|
} |
||||
|
$pageNames[$repositoryPath] = $name |
||||
|
|
||||
|
$content = Get-Content -LiteralPath $file.FullName -Raw -Encoding UTF8 |
||||
|
$title = if ($content -match '(?m)^#\s+(?<title>.+?)\s*$') { $Matches['title'] } else { $name -replace '-', ' ' } |
||||
|
[pscustomobject]@{ |
||||
|
Name = $name |
||||
|
Title = $title |
||||
|
Directory = $repositoryPath.Substring(0, $repositoryPath.LastIndexOf('/')) |
||||
|
Content = $content |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
# Remove the old pages, but keep the history of the wiki. |
||||
|
if (Test-Path -LiteralPath $destinationRoot) { |
||||
|
foreach ($item in Get-ChildItem -LiteralPath $destinationRoot -Force | Where-Object -Property Name -NE -Value '.git') { |
||||
|
if ($PSCmdlet.ShouldProcess($item.FullName, 'Remove')) { |
||||
|
Remove-Item -LiteralPath $item.FullName -Recurse -Force |
||||
|
} |
||||
|
} |
||||
|
} elseif ($PSCmdlet.ShouldProcess($destinationRoot, 'Create folder')) { |
||||
|
New-Item -ItemType Directory -Path $destinationRoot | Out-Null |
||||
|
} |
||||
|
|
||||
|
$wikiPages = [ordered]@{} |
||||
|
foreach ($page in $pages) { |
||||
|
$content = [regex]::Replace($page.Content, '\A---\r?\n.*?\r?\n---[ \t]*(?:\r?\n|\z)\s*', '', 'Singleline') |
||||
|
$wikiPages[$page.Name] = Convert-MarkdownLink -Markdown $content -Directory $page.Directory |
||||
|
} |
||||
|
|
||||
|
# The sidebar links the other pages and the cmdlets, grouped like the cmdlet list of the home page. |
||||
|
$homePage = $pages | Where-Object -Property Name -EQ -Value 'Home' |
||||
|
$sidebar = New-Object -TypeName 'System.Collections.Generic.List[string]' |
||||
|
$sidebar.Add('### [Home](Home)') |
||||
|
$sidebar.Add('') |
||||
|
foreach ($page in $pages | Where-Object { $_.Directory -eq $docsName -and $_.Name -ne 'Home' } | Sort-Object -Property Name) { |
||||
|
$sidebar.Add(('- [{0}]({1})' -f $page.Title, $page.Name)) |
||||
|
} |
||||
|
if ($homePage) { |
||||
|
$section = $null |
||||
|
foreach ($line in ($homePage.Content -split '\r?\n')) { |
||||
|
if ($line -match '^##\s+(?<title>.+?)\s*$') { |
||||
|
$section = $Matches['title'] |
||||
|
if ($section -eq 'Cmdlets') { |
||||
|
$sidebar.Add('') |
||||
|
$sidebar.Add('### Cmdlets') |
||||
|
} |
||||
|
} elseif ($section -eq 'Cmdlets' -and $line -match '^###\s+(?<title>.+?)\s*$') { |
||||
|
$sidebar.Add('') |
||||
|
$sidebar.Add(('**{0}**' -f $Matches['title'])) |
||||
|
$sidebar.Add('') |
||||
|
} elseif ($section -eq 'Cmdlets' -and $line -match '^\|\s*\[(?<name>[^\]]+)\]\((?<url>[^)\s]+)\)') { |
||||
|
$sidebar.Add(('- [{0}]({1})' -f $Matches['name'], (ConvertTo-WikiLink -Url $Matches['url'] -Directory $docsName))) |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
$wikiPages['_Sidebar'] = $sidebar -join "`n" |
||||
|
|
||||
|
$wikiPages['_Footer'] = ('This wiki is generated from the [{0}]({1}/tree/{2}/{0}) folder of the repository. ' + |
||||
|
'To change a page, edit its file there; changes made in the wiki are overwritten.') -f $docsName, $RepositoryUrl.TrimEnd('/'), $Branch |
||||
|
|
||||
|
# The former wiki page How-to-install is linked from outside; it now points to the installation steps. |
||||
|
if ($homePage -and $homePage.Content -match '(?m)^##\s+Installation\s*$') { |
||||
|
$wikiPages['How-to-install'] = "# How to install`n`nThe installation steps are in the [Installation](Home#installation) section of the [Home](Home) page." |
||||
|
} |
||||
|
|
||||
|
$encoding = New-Object -TypeName 'System.Text.UTF8Encoding' -ArgumentList $false |
||||
|
foreach ($name in $wikiPages.Keys) { |
||||
|
$file = Join-Path -Path $destinationRoot -ChildPath "$name.md" |
||||
|
if ($PSCmdlet.ShouldProcess($file, 'Write wiki page')) { |
||||
|
[IO.File]::WriteAllText($file, $wikiPages[$name].TrimEnd() + "`n", $encoding) |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,79 @@ |
|||||
|
<# |
||||
|
.SYNOPSIS |
||||
|
Runs the Pester tests in the Tests folder and reports the result to GitHub Actions. |
||||
|
|
||||
|
.DESCRIPTION |
||||
|
Imports Pester 5.7.1, runs the tests against the module build in NTFSSecurity\bin\Release, writes the result |
||||
|
file in the NUnit format, and adds the counts and the failed tests to the job summary of GitHub Actions. Fails if |
||||
|
a test or a test file fails. |
||||
|
|
||||
|
.PARAMETER ResultPath |
||||
|
Specifies the path of the result file. |
||||
|
|
||||
|
.PARAMETER Title |
||||
|
Specifies the heading of the test results in the job summary, such as the PowerShell edition. |
||||
|
|
||||
|
.EXAMPLE |
||||
|
.\.github\scripts\Invoke-Tests.ps1 -ResultPath TestResults\WindowsPowerShell.xml -Title 'Windows PowerShell 5.1' |
||||
|
|
||||
|
Runs the tests and writes the result file. Outside GitHub Actions, the script writes no job summary. |
||||
|
#> |
||||
|
[CmdletBinding()] |
||||
|
param ( |
||||
|
[Parameter(Mandatory)] |
||||
|
[ValidateNotNullOrEmpty()] |
||||
|
[string] |
||||
|
$ResultPath, |
||||
|
|
||||
|
[Parameter(Mandatory)] |
||||
|
[ValidateNotNullOrEmpty()] |
||||
|
[string] |
||||
|
$Title |
||||
|
) |
||||
|
|
||||
|
$ErrorActionPreference = 'Stop' |
||||
|
Import-Module -Name Pester -RequiredVersion 5.7.1 |
||||
|
|
||||
|
$resultFolder = Split-Path -Path $ResultPath -Parent |
||||
|
if ($resultFolder -and -not (Test-Path -LiteralPath $resultFolder)) { |
||||
|
New-Item -ItemType Directory -Path $resultFolder | Out-Null |
||||
|
} |
||||
|
|
||||
|
$configuration = New-PesterConfiguration |
||||
|
$configuration.Run.Path = Join-Path -Path $PSScriptRoot -ChildPath '..\..\Tests' |
||||
|
$configuration.Run.PassThru = $true |
||||
|
$configuration.Output.Verbosity = 'Detailed' |
||||
|
$configuration.TestResult.Enabled = $true |
||||
|
$configuration.TestResult.OutputFormat = 'NUnitXml' |
||||
|
$configuration.TestResult.OutputPath = $ResultPath |
||||
|
$result = Invoke-Pester -Configuration $configuration |
||||
|
|
||||
|
if ($env:GITHUB_STEP_SUMMARY) { |
||||
|
$summary = New-Object -TypeName 'System.Collections.Generic.List[string]' |
||||
|
$summary.Add("### Tests in $Title") |
||||
|
$summary.Add('') |
||||
|
$summary.Add('| Result | Passed | Failed | Skipped | Total |') |
||||
|
$summary.Add('| --- | ---: | ---: | ---: | ---: |') |
||||
|
$summary.Add(('| {0} | {1} | {2} | {3} | {4} |' -f $result.Result, $result.PassedCount, $result.FailedCount, |
||||
|
$result.SkippedCount, $result.TotalCount)) |
||||
|
if ($result.Failed.Count -gt 0 -or $result.FailedContainersCount -gt 0) { |
||||
|
$summary.Add('') |
||||
|
$summary.Add('Failed:') |
||||
|
$summary.Add('') |
||||
|
foreach ($test in $result.Failed) { |
||||
|
$message = "$(@($test.ErrorRecord)[0])" -replace '\s+', ' ' |
||||
|
$summary.Add(('- {0}: {1}' -f $test.ExpandedPath, $message)) |
||||
|
} |
||||
|
foreach ($container in $result.Containers | Where-Object -Property Result -EQ -Value 'Failed') { |
||||
|
$summary.Add(('- {0}: {1}' -f $container.Item, ("$(@($container.ErrorRecord)[0])" -replace '\s+', ' '))) |
||||
|
} |
||||
|
} |
||||
|
$summary.Add('') |
||||
|
|
||||
|
$encoding = New-Object -TypeName 'System.Text.UTF8Encoding' -ArgumentList $false |
||||
|
[IO.File]::AppendAllText($env:GITHUB_STEP_SUMMARY, ($summary -join "`n") + "`n", $encoding) |
||||
|
} |
||||
|
|
||||
|
if ($result.Result -ne 'Passed') { |
||||
|
throw "The tests in $Title failed: $($result.FailedCount) failed tests, $($result.FailedContainersCount) failed test files." |
||||
|
} |
||||
@ -0,0 +1,181 @@ |
|||||
|
# Builds the NTFSSecurity module from source, checks that the cmdlet |
||||
|
# documentation in Docs/Cmdlets and the help file generated from it match the |
||||
|
# cmdlets of that build, runs the Pester tests in Windows PowerShell 5.1 and |
||||
|
# PowerShell 7, and publishes Docs to the GitHub wiki from master. |
||||
|
name: CI |
||||
|
|
||||
|
on: |
||||
|
pull_request: |
||||
|
push: |
||||
|
branches: |
||||
|
- master |
||||
|
workflow_dispatch: |
||||
|
|
||||
|
permissions: |
||||
|
contents: read |
||||
|
|
||||
|
concurrency: |
||||
|
group: ci-${{ github.ref }} |
||||
|
cancel-in-progress: ${{ github.event_name == 'pull_request' }} |
||||
|
|
||||
|
jobs: |
||||
|
build: |
||||
|
name: Build and test |
||||
|
runs-on: windows-2025 |
||||
|
timeout-minutes: 30 |
||||
|
steps: |
||||
|
- name: Keep Windows line endings in the working tree |
||||
|
run: git config --global core.autocrlf true |
||||
|
|
||||
|
- name: Check out the repository |
||||
|
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 |
||||
|
with: |
||||
|
persist-credentials: false |
||||
|
|
||||
|
- name: Install platyPS, MarkdownLinkCheck, and Pester |
||||
|
shell: powershell |
||||
|
run: | |
||||
|
[Net.ServicePointManager]::SecurityProtocol = [Net.ServicePointManager]::SecurityProtocol -bor [Net.SecurityProtocolType]::Tls12 |
||||
|
Install-PackageProvider -Name NuGet -MinimumVersion 2.8.5.201 -Force | Out-Null |
||||
|
# Installed for all users, so that PowerShell 7 finds the modules, too |
||||
|
Install-Module -Name platyPS -RequiredVersion 0.14.2 -Scope AllUsers -Force |
||||
|
Install-Module -Name MarkdownLinkCheck -RequiredVersion 0.2.0 -Scope AllUsers -Force |
||||
|
# The image includes Pester 3.4.0, which is signed by a different publisher. |
||||
|
Install-Module -Name Pester -RequiredVersion 5.7.1 -Scope AllUsers -Force -SkipPublisherCheck |
||||
|
|
||||
|
- name: Restore the NuGet packages |
||||
|
shell: powershell |
||||
|
run: | |
||||
|
nuget restore NTFSSecurity\packages.config -PackagesDirectory packages -NonInteractive |
||||
|
if ($LASTEXITCODE -ne 0) { |
||||
|
throw "NuGet failed with exit code $LASTEXITCODE." |
||||
|
} |
||||
|
nuget restore Security2\packages.config -PackagesDirectory packages -NonInteractive |
||||
|
if ($LASTEXITCODE -ne 0) { |
||||
|
throw "NuGet failed with exit code $LASTEXITCODE." |
||||
|
} |
||||
|
# Provides the .NET Framework 4.5.2 reference assemblies, so the build does |
||||
|
# not depend on a targeting pack installed on the runner. |
||||
|
nuget install Microsoft.NETFramework.ReferenceAssemblies.net452 -Version 1.0.3 -OutputDirectory packages -NonInteractive |
||||
|
if ($LASTEXITCODE -ne 0) { |
||||
|
throw "NuGet failed with exit code $LASTEXITCODE." |
||||
|
} |
||||
|
|
||||
|
- name: Build the module |
||||
|
id: build |
||||
|
shell: powershell |
||||
|
run: | |
||||
|
# MSBuild isn't on the path of the runner; vswhere finds the one of Visual Studio. |
||||
|
$vswhere = Join-Path -Path ${env:ProgramFiles(x86)} -ChildPath 'Microsoft Visual Studio\Installer\vswhere.exe' |
||||
|
$msbuild = & $vswhere -latest -requires Microsoft.Component.MSBuild -find 'MSBuild\**\Bin\MSBuild.exe' | Select-Object -First 1 |
||||
|
if (-not $msbuild) { |
||||
|
throw 'MSBuild was not found.' |
||||
|
} |
||||
|
$referenceAssemblies = "$env:GITHUB_WORKSPACE\packages\Microsoft.NETFramework.ReferenceAssemblies.net452.1.0.3\build" |
||||
|
& $msbuild NTFSSecurity\NTFSSecurity.csproj /nologo /verbosity:minimal /p:Configuration=Release "/p:TargetFrameworkRootPath=$referenceAssemblies" "/p:FrameworkPathOverride=$referenceAssemblies\.NETFramework\v4.5.2" |
||||
|
if ($LASTEXITCODE -ne 0) { |
||||
|
throw "MSBuild failed with exit code $LASTEXITCODE." |
||||
|
} |
||||
|
|
||||
|
- name: Check the documentation against the build |
||||
|
shell: powershell |
||||
|
run: | |
||||
|
Import-Module -Name platyPS -RequiredVersion 0.14.2 |
||||
|
Import-Module -Name MarkdownLinkCheck -RequiredVersion 0.2.0 |
||||
|
Import-Module -Name .\NTFSSecurity\bin\Release\NTFSSecurity.psd1 -Force |
||||
|
|
||||
|
# 01. Test that the documentation matches the cmdlets built from source |
||||
|
Update-MarkdownHelp -Path ./Docs/Cmdlets | Out-Null |
||||
|
$diff = git diff -- Docs/Cmdlets |
||||
|
if ($diff) { |
||||
|
throw "Help is not up-to-date, run Update-MarkdownHelp: $diff" |
||||
|
} |
||||
|
|
||||
|
# 02. Verify hyperlinks |
||||
|
$brokenLinks = Get-MarkdownLink -Path .\Docs\ -BrokenOnly |
||||
|
if ($brokenLinks) { |
||||
|
throw "Found broken hyperlinks $brokenLinks" |
||||
|
} |
||||
|
|
||||
|
# 03. Test that the help file of the module matches the documentation |
||||
|
New-ExternalHelp -Path ./Docs/Cmdlets -OutputPath ./NTFSSecurity/en-US -Force | Out-Null |
||||
|
$helpChanges = git status --porcelain -- NTFSSecurity/en-US |
||||
|
if ($helpChanges) { |
||||
|
throw "The help file is not up-to-date, run New-ExternalHelp -Path .\Docs\Cmdlets -OutputPath .\NTFSSecurity\en-US -Force: $helpChanges" |
||||
|
} |
||||
|
|
||||
|
# 04. Run the Pester tests against the build, also when the documentation check failed |
||||
|
- name: Run the tests in Windows PowerShell 5.1 |
||||
|
if: ${{ !cancelled() && steps.build.outcome == 'success' }} |
||||
|
shell: powershell |
||||
|
run: .\.github\scripts\Invoke-Tests.ps1 -ResultPath TestResults\WindowsPowerShell.xml -Title 'Windows PowerShell 5.1' |
||||
|
|
||||
|
- name: Run the tests in PowerShell 7 |
||||
|
if: ${{ !cancelled() && steps.build.outcome == 'success' }} |
||||
|
shell: pwsh |
||||
|
run: ./.github/scripts/Invoke-Tests.ps1 -ResultPath TestResults/PowerShell7.xml -Title 'PowerShell 7' |
||||
|
|
||||
|
- name: Upload the test results |
||||
|
if: ${{ !cancelled() }} |
||||
|
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 |
||||
|
with: |
||||
|
name: test-results |
||||
|
path: TestResults/ |
||||
|
if-no-files-found: ignore |
||||
|
|
||||
|
wiki: |
||||
|
name: Wiki |
||||
|
needs: build |
||||
|
runs-on: ubuntu-latest |
||||
|
timeout-minutes: 10 |
||||
|
# Only this job can write: it publishes the wiki from master. On pull |
||||
|
# requests, it shows the pages that would change. |
||||
|
permissions: |
||||
|
contents: write |
||||
|
steps: |
||||
|
- name: Check out the repository |
||||
|
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 |
||||
|
with: |
||||
|
persist-credentials: false |
||||
|
|
||||
|
- name: Generate the wiki pages |
||||
|
shell: pwsh |
||||
|
env: |
||||
|
GH_TOKEN: ${{ github.token }} |
||||
|
run: | |
||||
|
gh auth setup-git |
||||
|
$wiki = Join-Path -Path $env:RUNNER_TEMP -ChildPath 'wiki' |
||||
|
git clone --quiet --depth 1 "$env:GITHUB_SERVER_URL/$env:GITHUB_REPOSITORY.wiki.git" $wiki |
||||
|
if ($LASTEXITCODE -ne 0) { |
||||
|
throw "Cloning the wiki failed with exit code $LASTEXITCODE." |
||||
|
} |
||||
|
./.github/scripts/Export-WikiContent.ps1 -Path ./Docs -DestinationPath $wiki -RepositoryUrl "$env:GITHUB_SERVER_URL/$env:GITHUB_REPOSITORY" |
||||
|
git -C $wiki add --all |
||||
|
$changes = @(git -C $wiki diff --cached --name-status) |
||||
|
$summary = if ($changes) { |
||||
|
@('### Wiki', '', 'Changed pages (A added, M modified, D deleted):', '', '```text') + $changes + @('```') |
||||
|
} else { |
||||
|
@('### Wiki', '', 'The wiki is up to date.') |
||||
|
} |
||||
|
Add-Content -LiteralPath $env:GITHUB_STEP_SUMMARY -Value $summary |
||||
|
Add-Content -LiteralPath $env:GITHUB_ENV -Value "WIKI_PATH=$wiki" |
||||
|
|
||||
|
- name: Publish the wiki |
||||
|
if: ${{ github.ref == 'refs/heads/master' && (github.event_name == 'push' || github.event_name == 'workflow_dispatch') }} |
||||
|
shell: pwsh |
||||
|
env: |
||||
|
GH_TOKEN: ${{ github.token }} |
||||
|
run: | |
||||
|
git -C $env:WIKI_PATH diff --cached --quiet |
||||
|
if ($LASTEXITCODE -eq 0) { |
||||
|
'The wiki is up to date.' |
||||
|
exit 0 |
||||
|
} |
||||
|
git -C $env:WIKI_PATH -c user.name='github-actions[bot]' -c user.email='41898282+github-actions[bot]@users.noreply.github.com' commit --quiet --message "Update from $env:GITHUB_SHA" |
||||
|
if ($LASTEXITCODE -ne 0) { |
||||
|
throw "Committing the wiki failed with exit code $LASTEXITCODE." |
||||
|
} |
||||
|
git -C $env:WIKI_PATH push --quiet |
||||
|
if ($LASTEXITCODE -ne 0) { |
||||
|
throw "Publishing the wiki failed with exit code $LASTEXITCODE." |
||||
|
} |
||||
@ -0,0 +1,27 @@ |
|||||
|
--- |
||||
|
status: accepted |
||||
|
date: 2026-10-04 |
||||
|
last-verified: 2026-10-04 |
||||
|
owner: shared |
||||
|
source: maintainer decision after work package 4 |
||||
|
--- |
||||
|
|
||||
|
# Decision 11: CI and the wiki run on GitHub Actions |
||||
|
|
||||
|
- Choice: `.github/workflows/ci.yml` replaces AppVeyor. On pull requests and |
||||
|
pushes to `master`, the `build` job (`windows-2025`) builds the module in |
||||
|
Release, checks the docs against the build, and runs the Pester tests in |
||||
|
Windows PowerShell 5.1 and PowerShell 7. The `wiki` job converts `Docs` |
||||
|
with `.github/scripts/Export-WikiContent.ps1` and publishes the wiki from |
||||
|
`master` with the built-in token; on pull requests it lists the pages that |
||||
|
would change. `appveyor.yml` is removed. |
||||
|
- Rationale: The maintainer wants a browsable wiki without a second, hand- |
||||
|
written copy of the docs (the 2018 wiki went stale), and one CI platform |
||||
|
instead of two. Public repositories get Windows runners for free, and the |
||||
|
checks appear on the pull request without a third-party service. |
||||
|
- Consequences: `Docs` stays the only source (Decision 9); the wiki is a |
||||
|
generated mirror, and edits made in the wiki are overwritten. Only the |
||||
|
`wiki` job has `contents: write`; actions are pinned by commit SHA. Test |
||||
|
results appear in the job summary and as the `test-results` artifact. |
||||
|
- Rejected: a hand-maintained wiki next to `Docs`, publishing the wiki by |
||||
|
hand at release time, and keeping AppVeyor for build and tests. |
||||
@ -0,0 +1,228 @@ |
|||||
|
<# |
||||
|
Tests .github\scripts\Export-WikiContent.ps1, which converts the documentation in Docs into the pages of the |
||||
|
GitHub wiki: with a small sample of Docs for the conversion rules, and with the real Docs for complete pages and |
||||
|
working links. |
||||
|
#> |
||||
|
[Diagnostics.CodeAnalysis.SuppressMessageAttribute( |
||||
|
'PSUseDeclaredVarsMoreThanAssignments', '', Justification = 'Pester shares variables between blocks.' |
||||
|
)] |
||||
|
param () |
||||
|
|
||||
|
BeforeAll { |
||||
|
$exportScript = Join-Path -Path $PSScriptRoot -ChildPath '..\.github\scripts\Export-WikiContent.ps1' |
||||
|
|
||||
|
function Get-GitHubAnchor { |
||||
|
# Returns the anchors that GitHub generates for the headings of a Markdown text. |
||||
|
param ( |
||||
|
[Parameter(Mandatory)] |
||||
|
[string] |
||||
|
$Markdown |
||||
|
) |
||||
|
|
||||
|
$text = [regex]::Replace($Markdown, '(?ms)^```.*?^```', '') |
||||
|
$seen = @{} |
||||
|
foreach ($match in [regex]::Matches($text, '(?m)^#{1,6}\s+(.+?)\s*#*\s*$')) { |
||||
|
$heading = $match.Groups[1].Value -replace '\[([^\]]*)\]\([^)]*\)', '$1' -replace '[`*]', '' |
||||
|
$anchor = [regex]::Replace($heading.Trim().ToLowerInvariant(), '[^\p{L}\p{Nd}\s_-]', '') -replace ' ', '-' |
||||
|
if ($seen.ContainsKey($anchor)) { |
||||
|
$seen[$anchor]++ |
||||
|
'{0}-{1}' -f $anchor, $seen[$anchor] |
||||
|
} else { |
||||
|
$seen[$anchor] = 0 |
||||
|
$anchor |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
Describe 'Export-WikiContent.ps1' { |
||||
|
Context 'When it converts a sample of Docs' { |
||||
|
BeforeAll { |
||||
|
$repositoryPath = Join-Path -Path $TestDrive -ChildPath 'repository' |
||||
|
$docsPath = Join-Path -Path $repositoryPath -ChildPath 'Docs' |
||||
|
$wikiPath = Join-Path -Path $TestDrive -ChildPath 'wiki' |
||||
|
foreach ($folder in "$docsPath\Cmdlets", "$docsPath\Contributing", "$wikiPath\.git") { |
||||
|
New-Item -ItemType Directory -Path $folder -Force | Out-Null |
||||
|
} |
||||
|
|
||||
|
# A clone of the wiki with pages that Docs doesn't have |
||||
|
Set-Content -LiteralPath "$wikiPath\.git\config" -Value '[core]' |
||||
|
Set-Content -LiteralPath "$wikiPath\Version-History.textile" -Value '* 4.2.4' |
||||
|
Set-Content -LiteralPath "$wikiPath\Old-Page.md" -Value '# Old page' |
||||
|
|
||||
|
Set-Content -LiteralPath "$repositoryPath\CHANGELOG.md" -Value '# Changelog' |
||||
|
Set-Content -LiteralPath "$docsPath\Contributing.md" -Value '# Contributing' |
||||
|
Set-Content -LiteralPath "$docsPath\Contributing\01-Getting-Started.md" -Value '# Get started' |
||||
|
Set-Content -LiteralPath "$docsPath\Concepts.md" -Value "# Concepts`n`n## Rights`n`nSee [Home](README.md)." |
||||
|
Set-Content -LiteralPath "$docsPath\README.md" -Value @' |
||||
|
# Thing |
||||
|
|
||||
|
[Concepts](Concepts.md#rights), [Get-Thing](Cmdlets/Get-Thing.md), and [`Set-Thing`](Cmdlets/Set-Thing.md). |
||||
|
[Changelog](../CHANGELOG.md), [guide](Contributing.md), and [first steps](Contributing/01-Getting-Started.md). |
||||
|
[Example site](https://example.com/page.md) and [cmdlets](#cmdlets). |
||||
|
Code keeps its links: `[Concepts](Concepts.md)`. |
||||
|
|
||||
|
```powershell |
||||
|
# [Concepts](Concepts.md) |
||||
|
``` |
||||
|
|
||||
|
## Installation |
||||
|
|
||||
|
Install the module. |
||||
|
|
||||
|
## Cmdlets |
||||
|
|
||||
|
### Getting |
||||
|
|
||||
|
| Cmdlet | Description | |
||||
|
| --- | --- | |
||||
|
| [Get-Thing](Cmdlets/Get-Thing.md) | Gets a thing. | |
||||
|
|
||||
|
### Setting |
||||
|
|
||||
|
| Cmdlet | Description | |
||||
|
| --- | --- | |
||||
|
| [Set-Thing](Cmdlets/Set-Thing.md) | Sets a thing. | |
||||
|
'@ |
||||
|
$metadata = "---`nexternal help file: Thing.dll-Help.xml`nonline version: https://example.com`nschema: 2.0.0`n---`n`n" |
||||
|
Set-Content -LiteralPath "$docsPath\Cmdlets\Get-Thing.md" -Value ($metadata + |
||||
|
"# Get-Thing`n`nSee [rights](../Concepts.md#rights), [Set-Thing](Set-Thing.md), and [home](../README.md).") |
||||
|
Set-Content -LiteralPath "$docsPath\Cmdlets\Set-Thing.md" -Value ($metadata + |
||||
|
"# Set-Thing`n`nSee [Get-Thing](Get-Thing.md).") |
||||
|
|
||||
|
& $exportScript -Path $docsPath -DestinationPath $wikiPath -RepositoryUrl 'https://github.com/contoso/Thing' -Branch 'main' |
||||
|
|
||||
|
$homePage = Get-Content -LiteralPath "$wikiPath\Home.md" -Raw |
||||
|
$sidebar = Get-Content -LiteralPath "$wikiPath\_Sidebar.md" -Raw |
||||
|
} |
||||
|
|
||||
|
It 'Should write Home, Concepts, the cmdlet pages, How-to-install, the sidebar, and the footer' { |
||||
|
$expected = 'Home.md', 'Concepts.md', 'Get-Thing.md', 'Set-Thing.md', 'How-to-install.md', '_Sidebar.md', '_Footer.md' |
||||
|
|
||||
|
((Get-ChildItem -LiteralPath $wikiPath -File).Name | Sort-Object) -join ', ' | |
||||
|
Should -BeExactly (($expected | Sort-Object) -join ', ') |
||||
|
} |
||||
|
|
||||
|
It 'Should keep the .git folder and remove the pages that Docs does not have' { |
||||
|
"$wikiPath\.git\config" | Should -Exist |
||||
|
"$wikiPath\Version-History.textile" | Should -Not -Exist |
||||
|
"$wikiPath\Old-Page.md" | Should -Not -Exist |
||||
|
} |
||||
|
|
||||
|
It 'Should not publish the contributor guide' { |
||||
|
Get-ChildItem -LiteralPath $wikiPath -Recurse -File -Filter '*Get*Started*' | Should -BeNullOrEmpty |
||||
|
"$wikiPath\Contributing.md" | Should -Not -Exist |
||||
|
} |
||||
|
|
||||
|
It 'Should convert <Link> on <Page> into <Expected>' -ForEach @( |
||||
|
@{ Page = 'Home'; Link = '[Concepts](Concepts.md#rights)'; Expected = '[Concepts](Concepts#rights)' } |
||||
|
@{ Page = 'Home'; Link = '[Get-Thing](Cmdlets/Get-Thing.md)'; Expected = '[Get-Thing](Get-Thing)' } |
||||
|
@{ Page = 'Home'; Link = '[`Set-Thing`](Cmdlets/Set-Thing.md)'; Expected = '[`Set-Thing`](Set-Thing)' } |
||||
|
@{ Page = 'Home'; Link = '[Changelog](../CHANGELOG.md)'; Expected = '[Changelog](https://github.com/contoso/Thing/blob/main/CHANGELOG.md)' } |
||||
|
@{ Page = 'Home'; Link = '[guide](Contributing.md)'; Expected = '[guide](https://github.com/contoso/Thing/blob/main/Docs/Contributing.md)' } |
||||
|
@{ Page = 'Home'; Link = '[first steps](Contributing/01-Getting-Started.md)'; Expected = '[first steps](https://github.com/contoso/Thing/blob/main/Docs/Contributing/01-Getting-Started.md)' } |
||||
|
@{ Page = 'Home'; Link = '[Example site](https://example.com/page.md)'; Expected = '[Example site](https://example.com/page.md)' } |
||||
|
@{ Page = 'Home'; Link = '[cmdlets](#cmdlets)'; Expected = '[cmdlets](#cmdlets)' } |
||||
|
@{ Page = 'Concepts'; Link = '[Home](README.md)'; Expected = '[Home](Home)' } |
||||
|
@{ Page = 'Get-Thing'; Link = '[rights](../Concepts.md#rights)'; Expected = '[rights](Concepts#rights)' } |
||||
|
@{ Page = 'Get-Thing'; Link = '[Set-Thing](Set-Thing.md)'; Expected = '[Set-Thing](Set-Thing)' } |
||||
|
@{ Page = 'Get-Thing'; Link = '[home](../README.md)'; Expected = '[home](Home)' } |
||||
|
) { |
||||
|
$content = Get-Content -LiteralPath "$wikiPath\$Page.md" -Raw |
||||
|
|
||||
|
$content | Should -Match ([regex]::Escape($Expected)) |
||||
|
if ($Link -ne $Expected) { |
||||
|
$content | Should -Not -Match ([regex]::Escape($Link)) |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
It 'Should keep links in code unchanged' { |
||||
|
$homePage | Should -Match ([regex]::Escape('`[Concepts](Concepts.md)`')) |
||||
|
$homePage | Should -Match ([regex]::Escape('# [Concepts](Concepts.md)')) |
||||
|
} |
||||
|
|
||||
|
It 'Should remove the platyPS metadata from the cmdlet pages' { |
||||
|
Get-Content -LiteralPath "$wikiPath\Get-Thing.md" -TotalCount 1 | Should -BeExactly '# Get-Thing' |
||||
|
} |
||||
|
|
||||
|
It 'Should link Home and the other pages in the sidebar' { |
||||
|
$sidebar | Should -Match ([regex]::Escape('[Home](Home)')) |
||||
|
$sidebar | Should -Match ([regex]::Escape('[Concepts](Concepts)')) |
||||
|
} |
||||
|
|
||||
|
It 'Should list the cmdlets in the sidebar under the groups of Docs/README.md' { |
||||
|
$sidebar | Should -Match '(?s)Getting.*\[Get-Thing\]\(Get-Thing\).*Setting.*\[Set-Thing\]\(Set-Thing\)' |
||||
|
} |
||||
|
|
||||
|
It 'Should say in the footer that the wiki is generated from Docs' { |
||||
|
Get-Content -LiteralPath "$wikiPath\_Footer.md" -Raw | |
||||
|
Should -Match ([regex]::Escape('(https://github.com/contoso/Thing/tree/main/Docs)')) |
||||
|
} |
||||
|
|
||||
|
It 'Should keep the address of the former page How-to-install' { |
||||
|
Get-Content -LiteralPath "$wikiPath\How-to-install.md" -Raw | Should -Match ([regex]::Escape('(Home#installation)')) |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
Context 'When it converts the documentation of the repository' { |
||||
|
BeforeAll { |
||||
|
$repositoryPath = Join-Path -Path $PSScriptRoot -ChildPath '..' |
||||
|
$docsPath = Join-Path -Path $repositoryPath -ChildPath 'Docs' |
||||
|
$wikiPath = Join-Path -Path $TestDrive -ChildPath 'wiki-of-the-repository' |
||||
|
|
||||
|
& $exportScript -Path $docsPath -DestinationPath $wikiPath |
||||
|
|
||||
|
$pages = Get-ChildItem -LiteralPath $wikiPath -Filter '*.md' |
||||
|
$cmdletNames = (Get-ChildItem -LiteralPath (Join-Path -Path $docsPath -ChildPath 'Cmdlets') -Filter '*.md').BaseName |
||||
|
} |
||||
|
|
||||
|
It 'Should write a page for every page in Docs except the contributor guide, and one for every cmdlet' { |
||||
|
$topPages = (Get-ChildItem -LiteralPath $docsPath -Filter '*.md' | |
||||
|
Where-Object -Property Name -NotIn -Value 'README.md', 'Contributing.md').BaseName |
||||
|
$expected = @('Home', 'How-to-install', '_Sidebar', '_Footer') + $topPages + $cmdletNames |
||||
|
|
||||
|
($pages.BaseName | Sort-Object) -join ', ' | Should -BeExactly (($expected | Sort-Object) -join ', ') |
||||
|
} |
||||
|
|
||||
|
It 'Should keep the page name Version-History, which the release notes of 4.2.4 and 4.2.6 link to' { |
||||
|
Join-Path -Path $wikiPath -ChildPath 'Version-History.md' | Should -Exist |
||||
|
} |
||||
|
|
||||
|
It 'Should list every cmdlet in the sidebar' { |
||||
|
$sidebar = Get-Content -LiteralPath (Join-Path -Path $wikiPath -ChildPath '_Sidebar.md') -Raw |
||||
|
|
||||
|
$cmdletNames | Where-Object -FilterScript { $sidebar -notmatch ('\]\({0}\)' -f [regex]::Escape($_)) } | |
||||
|
Should -BeNullOrEmpty |
||||
|
} |
||||
|
|
||||
|
It 'Should link only to existing wiki pages, their anchors, and existing files of the repository' { |
||||
|
$anchors = @{} |
||||
|
foreach ($page in $pages) { |
||||
|
$anchors[$page.BaseName] = @(Get-GitHubAnchor -Markdown (Get-Content -LiteralPath $page.FullName -Raw)) |
||||
|
} |
||||
|
|
||||
|
$brokenLinks = foreach ($page in $pages) { |
||||
|
$text = [regex]::Replace((Get-Content -LiteralPath $page.FullName -Raw), '(?ms)^```.*?^```', '') |
||||
|
$text = [regex]::Replace($text, '`[^`\n]+`', '') |
||||
|
foreach ($match in [regex]::Matches($text, '\]\((?<url>[^)\s]+)\)')) { |
||||
|
$url = $match.Groups['url'].Value |
||||
|
if ($url -match '^https://github\.com/raandree/NTFSSecurity/(?:blob|tree)/master/(?<path>[^#]+)') { |
||||
|
if (-not (Test-Path -LiteralPath (Join-Path -Path $repositoryPath -ChildPath $Matches['path']))) { |
||||
|
'{0}: {1}' -f $page.BaseName, $url |
||||
|
} |
||||
|
} elseif ($url -notmatch '^[a-z]+:') { |
||||
|
$target, $anchor = $url -split '#', 2 |
||||
|
if (-not $target) { |
||||
|
$target = $page.BaseName |
||||
|
} |
||||
|
if (-not $anchors.ContainsKey($target) -or ($anchor -and $anchors[$target] -notcontains $anchor)) { |
||||
|
'{0}: {1}' -f $page.BaseName, $url |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
$brokenLinks | Should -BeNullOrEmpty |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -1,86 +0,0 @@ |
|||||
# Builds the NTFSSecurity module from source, checks that the cmdlet |
|
||||
# documentation in Docs/Cmdlets and the help file generated from it match the |
|
||||
# cmdlets of that build, and runs the Pester tests against the build. |
|
||||
image: Visual Studio 2022 |
|
||||
|
|
||||
init: |
|
||||
- ps: git config --global core.autocrlf true |
|
||||
|
|
||||
install: |
|
||||
- ps: | |
|
||||
[Net.ServicePointManager]::SecurityProtocol = [Net.ServicePointManager]::SecurityProtocol -bor [Net.SecurityProtocolType]::Tls12 |
|
||||
Install-PackageProvider -Name NuGet -MinimumVersion 2.8.5.201 -Force | Out-Null |
|
||||
Install-Module -Name platyPS -RequiredVersion 0.14.2 -Force |
|
||||
Install-Module -Name MarkdownLinkCheck -RequiredVersion 0.2.0 -Force |
|
||||
# The image includes Pester 3.4.0, which is signed by a different publisher. |
|
||||
Install-Module -Name Pester -RequiredVersion 5.7.1 -Force -SkipPublisherCheck |
|
||||
|
|
||||
before_build: |
|
||||
- nuget restore NTFSSecurity\packages.config -PackagesDirectory packages -NonInteractive |
|
||||
- nuget restore Security2\packages.config -PackagesDirectory packages -NonInteractive |
|
||||
# Provides the .NET Framework 4.5.2 reference assemblies, so the build does |
|
||||
# not depend on a targeting pack installed on the build image. |
|
||||
- nuget install Microsoft.NETFramework.ReferenceAssemblies.net452 -Version 1.0.3 -OutputDirectory packages -NonInteractive |
|
||||
|
|
||||
build_script: |
|
||||
- ps: | |
|
||||
$referenceAssemblies = "$env:APPVEYOR_BUILD_FOLDER\packages\Microsoft.NETFramework.ReferenceAssemblies.net452.1.0.3\build" |
|
||||
msbuild NTFSSecurity\NTFSSecurity.csproj /nologo /verbosity:minimal /p:Configuration=Release "/p:TargetFrameworkRootPath=$referenceAssemblies" "/p:FrameworkPathOverride=$referenceAssemblies\.NETFramework\v4.5.2" |
|
||||
if ($LASTEXITCODE -ne 0) { |
|
||||
throw "MSBuild failed with exit code $LASTEXITCODE." |
|
||||
} |
|
||||
|
|
||||
test_script: |
|
||||
- ps: | |
|
||||
$ErrorActionPreference = 'Stop' |
|
||||
Import-Module -Name platyPS |
|
||||
Import-Module -Name MarkdownLinkCheck |
|
||||
Import-Module -Name .\NTFSSecurity\bin\Release\NTFSSecurity.psd1 -Force |
|
||||
|
|
||||
# 01. Test that the documentation matches the cmdlets built from source |
|
||||
Update-MarkdownHelp -Path ./Docs/Cmdlets | Out-Null |
|
||||
$diff = git diff -- Docs/Cmdlets |
|
||||
if ($diff) { |
|
||||
throw "Help is not up-to-date, run Update-MarkdownHelp: $diff" |
|
||||
} |
|
||||
|
|
||||
# 02. Verify hyperlinks |
|
||||
$brokenLinks = Get-MarkdownLink -Path .\Docs\ -BrokenOnly |
|
||||
if ($brokenLinks) { |
|
||||
throw "Found broken hyperlinks $brokenLinks" |
|
||||
} |
|
||||
|
|
||||
# 03. Test that the help file of the module matches the documentation |
|
||||
New-ExternalHelp -Path ./Docs/Cmdlets -OutputPath ./NTFSSecurity/en-US -Force | Out-Null |
|
||||
$helpChanges = git status --porcelain -- NTFSSecurity/en-US |
|
||||
if ($helpChanges) { |
|
||||
throw "The help file is not up-to-date, run New-ExternalHelp -Path .\Docs\Cmdlets -OutputPath .\NTFSSecurity\en-US -Force: $helpChanges" |
|
||||
} |
|
||||
|
|
||||
# 04. Run the Pester tests against the module build |
|
||||
Import-Module -Name Pester -RequiredVersion 5.7.1 |
|
||||
$configuration = New-PesterConfiguration |
|
||||
$configuration.Run.Path = '.\Tests' |
|
||||
$configuration.Run.PassThru = $true |
|
||||
$configuration.Output.Verbosity = 'Detailed' |
|
||||
$result = Invoke-Pester -Configuration $configuration |
|
||||
|
|
||||
# Report every test once on the Tests tab. An uploaded NUnit file lists a |
|
||||
# Pester 5 test once for each block that contains it. |
|
||||
if ($env:APPVEYOR_API_URL -and $result.Tests) { |
|
||||
$tests = @(foreach ($test in $result.Tests) { |
|
||||
@{ |
|
||||
testName = $test.ExpandedPath |
|
||||
testFramework = 'Pester' |
|
||||
fileName = Split-Path -Path $test.ScriptBlock.File -Leaf |
|
||||
outcome = if ($test.Result -eq 'NotRun') { 'NotRunnable' } else { "$($test.Result)" } |
|
||||
durationMilliseconds = [long] $test.Duration.TotalMilliseconds |
|
||||
ErrorMessage = @($test.ErrorRecord | ForEach-Object -Process { "$_" }) -join [Environment]::NewLine |
|
||||
} |
|
||||
}) |
|
||||
$body = [Text.Encoding]::UTF8.GetBytes((ConvertTo-Json -InputObject $tests -Compress)) |
|
||||
Invoke-RestMethod -Method Post -Uri ($env:APPVEYOR_API_URL.TrimEnd('/') + '/api/tests/batch') -Body $body -ContentType 'application/json; charset=utf-8' | Out-Null |
|
||||
} |
|
||||
if ($result.FailedCount -gt 0) { |
|
||||
throw "$($result.FailedCount) Pester tests failed." |
|
||||
} |
|
||||
Loading…
Reference in new issue