commit dcb3f40371e32a258311d1720f74a242817f6400 Author: Kevin Marquette Date: Sun Mar 25 15:39:30 2018 -0700 Initial commit diff --git a/BuildTasks/Analyze.Task.ps1 b/BuildTasks/Analyze.Task.ps1 new file mode 100644 index 0000000..73825e3 --- /dev/null +++ b/BuildTasks/Analyze.Task.ps1 @@ -0,0 +1,17 @@ +task Analyze { + $params = @{ + IncludeDefaultRules = $true + Path = $ManifestPath + Settings = "$BuildRoot\ScriptAnalyzerSettings.psd1" + Severity = 'Warning' + } + + "Analyzing $ManifestPath..." + $results = Invoke-ScriptAnalyzer @params + if ($results) + { + 'One or more PSScriptAnalyzer errors/warnings were found.' + 'Please investigate or add the required SuppressMessage attribute.' + $results | Format-Table -AutoSize + } +} diff --git a/BuildTasks/BuildManifest.Task.ps1 b/BuildTasks/BuildManifest.Task.ps1 new file mode 100644 index 0000000..755d3f1 --- /dev/null +++ b/BuildTasks/BuildManifest.Task.ps1 @@ -0,0 +1,103 @@ + +taskx BuildManifest @{ + Inputs = (Get-ChildItem -Path $Source -Recurse -File) + Outputs = $ManifestPath + Jobs = { + "Updating [$ManifestPath]..." + Copy-Item -Path "$Source\$ModuleName.psd1" -Destination $ManifestPath + + $functions = Get-ChildItem -Path "$ModuleName\Public\*.ps1" -ErrorAction 'Ignore' | + Where-Object 'Name' -notmatch 'Tests' + + if ($functions) + { + 'Setting FunctionsToExport...' + Set-ModuleFunctions -Name $ManifestPath -FunctionsToExport $functions.BaseName + } + + 'Detecting semantic versioning...' + "Importing Module [$ManifestPath]..." + Import-Module -FullyQualifiedName $ManifestPath + + "Get-Command -Module [$ModuleName]..." + $commands = Get-Command -Module $ModuleName + + "Removing Module [$ModuleName]..." + Remove-Module -Name $ModuleName -Force + + 'Calculating fingerprint...' + $fingerprint = foreach ($command in $commands) + { + foreach ($parameter in $command.Parameters.Keys) + { + '{0}:{1}' -f $command.Name, $command.Parameters[$parameter].Name + foreach ($alias in $command.Parameters[$parameter].Aliases) + { + '{0}:{1}' -f $command.Name, $alias + } + } + } + + $fingerprint = $fingerprint | Sort-Object + + if (Test-Path -Path '.\fingerprint') + { + $oldFingerprint = Get-Content -Path '.\fingerprint' + } + + $bumpVersionType = 'Patch' + + 'Detecting new features...' + $features = $fingerprint | + Where-Object { $_ -notin $oldFingerprint } + + foreach ($feature in $features) + { + $feature + $bumpVersionType = 'Minor' + } + + 'Detecting breaking changes...' + $breakingChanges = $oldFingerprint | + Where-Object { $_ -notin $fingerprint } + + foreach ($breakingChange in $breakingChanges) + { + $breakingChange + $bumpVersionType = 'Major' + } + + Set-Content -Path '.\fingerprint' -Value $fingerprint + + # Bump the module version + $version = [version] (Get-Metadata -Path $manifestPath -PropertyName 'ModuleVersion') + + if ($version -lt ([version] '1.0.0')) + { + "Module is still in beta; don't bump major version." + if ($bumpVersionType -eq 'Major') + { + $bumpVersionType = 'Minor' + } + else + { + $bumpVersionType = 'Patch' + } + } + + "Stepping [$bumpVersionType] version [$version]..." + $version = [version] (Step-Version -Version $version -Type $bumpVersionType) + + $build = 0 + if ($null -ne $env:Build_BuildID) + { + $build = $env:Build_BuildID + } + + $version = [version]::new($version.Major, $version.Minor, $version.Build, $build) + "Using version [$version]..." + "##vso[build.updatebuildnumber]$version" + + Update-Metadata -Path $ManifestPath -PropertyName 'ModuleVersion' -Value $version + } +} diff --git a/BuildTasks/BuildModule.Task.ps1 b/BuildTasks/BuildModule.Task.ps1 new file mode 100644 index 0000000..b55e961 --- /dev/null +++ b/BuildTasks/BuildModule.Task.ps1 @@ -0,0 +1,91 @@ + +function Import-ClassOrder +{ + [cmdletbinding()] + param($cName,$Map) + Write-Verbose "Checking on [$cName]" + if($Map.ContainsKey($cName) -and $Map[$cName].Imported -ne $true) + { + if($Map[$cName].Base) + { + Write-Verbose " Base class [$($Map[$cName].Base)]" + Import-ClassOrder $Map[$cName].Base $Map + } + $cPath = $Map[$cName].Path + Write-Verbose "Dot Sourcing [$cPath]" + $cPath + $Map[$cName].Imported = $true + } +} + +taskx BuildModule @{ + Inputs = (Get-ChildItem -Path $Source -Recurse -Filter *.ps1) + Outputs = $ModulePath + Jobs = { + $sb = [Text.StringBuilder]::new() + $null = $sb.AppendLine('$Script:PSModuleRoot = $PSScriptRoot') + + # Class importer + $root = Join-Path -Path $source -ChildPath 'Classes' + "Load classes from [$root]" + $classFiles = Get-ChildItem -Path $root -Filter '*.ps1' -Recurse | + Where-Object Name -notlike '*.Tests.ps1' + + $classes = @{} + + foreach($file in $classFiles) + { + $name = $file.BaseName + $classes[$name] = @{ + Name = $name + Path = $file.FullName + } + $data = Get-Content $file.fullname + foreach($line in $data) + { + if($line -match "class\s+($Name)\s*:\s*(?\w*)") + { + $classes[$name].Base = $Matches.baseclass + } + } + } + + $importOrder = foreach($className in $classes.Keys) + { + Import-ClassOrder $className $classes + } + + foreach($class in $importOrder) + { + "Importing [$class]..." + $null = $sb.AppendLine("# .$class") + $null = $sb.AppendLine([IO.File]::ReadAllText($class)) + } + + foreach ($folder in ($Folders -ne 'Classes')) + { + if (Test-Path -Path "$Source\$folder") + { + $null = $sb.AppendLine("# Importing from [$Source\$folder]") + $files = Get-ChildItem -Path "$Source\$folder\*.ps1" | + Where-Object 'Name' -notlike '*.Tests.ps1' + + foreach ($file in $files) + { + $name = $file.Fullname.Replace($buildroot, '') + + "Importing [$($file.FullName)]..." + $null = $sb.AppendLine("# .$name") + $null = $sb.AppendLine([IO.File]::ReadAllText($file.FullName)) + } + } + } + + "Creating Module [$ModulePath]..." + $null = New-Item -Path (Split-path $ModulePath) -ItemType Directory -ErrorAction SilentlyContinue -Force + Set-Content -Path $ModulePath -Value $sb.ToString() -Encoding 'UTF8' + + 'Moving "#Requires" statements and "using" directives...' + Move-Statement -Path $ModulePath -Type 'Comment', 'Keyword' -Token '#Requires', 'using' -Index 0 + } +} diff --git a/BuildTasks/Clean.Task.ps1 b/BuildTasks/Clean.Task.ps1 new file mode 100644 index 0000000..59b3ebf --- /dev/null +++ b/BuildTasks/Clean.Task.ps1 @@ -0,0 +1,9 @@ +task Clean { + 'Cleaning Output files...' + $null = Get-ChildItem -Path $Output -File -Recurse | + Remove-Item -Force -ErrorAction 'Ignore' + + 'Cleaning Output directories...' + $null = Get-ChildItem -Path $Output -Directory -Recurse | + Remove-Item -Recurse -Force -ErrorAction 'Ignore' +} diff --git a/BuildTasks/Copy.Task.ps1 b/BuildTasks/Copy.Task.ps1 new file mode 100644 index 0000000..9a43b8e --- /dev/null +++ b/BuildTasks/Copy.Task.ps1 @@ -0,0 +1,23 @@ + +task Copy { + "Creating Directory [$Destination]..." + $null = New-Item -ItemType 'Directory' -Path $Destination -ErrorAction 'Ignore' + + $files = Get-ChildItem -Path $Source -File | + Where-Object 'Name' -notmatch "$ModuleName\.ps[dm]1" + + foreach ($file in $files) + { + 'Creating [.{0}]...' -f $file.FullName.Replace($buildroot, '') + Copy-Item -Path $file.FullName -Destination $Destination -Force + } + + $directories = Get-ChildItem -Path $Source -Directory | + Where-Object 'Name' -notin $Folders + + foreach ($directory in $directories) + { + 'Creating [.{0}]...' -f $directory.FullName.Replace($buildroot, '') + Copy-Item -Path $directory.FullName -Destination $Destination -Recurse -Force + } +} diff --git a/BuildTasks/FullTests.Task.ps1 b/BuildTasks/FullTests.Task.ps1 new file mode 100644 index 0000000..ac4ccfa --- /dev/null +++ b/BuildTasks/FullTests.Task.ps1 @@ -0,0 +1,25 @@ +task FullTests { + $params = @{ + CodeCoverage = 'Output\*\*.psm1' + CodeCoverageOutputFile = 'Output\codecoverage.xml' + OutputFile = $testFile + OutputFormat = 'NUnitXml' + PassThru = $true + Path = 'Tests' + Show = 'Failed', 'Fails', 'Summary' + Tag = 'Build' + } + + $results = Invoke-Pester @params + if ($results.FailedCount -gt 0) + { + Write-Error -Message "Failed [$($results.FailedCount)] Pester tests." + } + + $requiredPercent = 0.70 + $codeCoverage = $results.codecoverage.NumberOfCommandsExecuted / $results.codecoverage.NumberOfCommandsAnalyzed + if($codeCoverage -lt $requiredPercent) + { + Write-Error ("Failed Code Coverage [{0:P}] below {1:P}" -f $codeCoverage,$requiredPercent) + } +} diff --git a/BuildTasks/GenerateHelp.Task.ps1 b/BuildTasks/GenerateHelp.Task.ps1 new file mode 100644 index 0000000..51d3a36 --- /dev/null +++ b/BuildTasks/GenerateHelp.Task.ps1 @@ -0,0 +1,23 @@ + +task GenerateHelp { + if (-not(Get-ChildItem -Path $DocsPath -Filter '*.md' -Recurse -ErrorAction 'Ignore')) + { + "No Markdown help files to process. Skipping help file generation..." + return + } + + $locales = (Get-ChildItem -Path $DocsPath -Directory).Name + foreach ($locale in $locales) + { + $params = @{ + ErrorAction = 'SilentlyContinue' + Force = $true + OutputPath = "$Destination\en-US" + Path = "$DocsPath\en-US" + } + + # Generate the module's primary MAML help file. + "Creating new External help for [$ModuleName]..." + $null = New-ExternalHelp @params + } +} diff --git a/BuildTasks/GenerateMarkdown.Task.ps1 b/BuildTasks/GenerateMarkdown.Task.ps1 new file mode 100644 index 0000000..c1181ec --- /dev/null +++ b/BuildTasks/GenerateMarkdown.Task.ps1 @@ -0,0 +1,41 @@ + +task GenerateMarkdown { + $module = Import-Module -FullyQualifiedName $ManifestPath -Force -PassThru + + try + { + if ($module.ExportedFunctions.Count -eq 0) + { + 'No functions have been exported for this module. Skipping Markdown generation...' + return + } + + if (Get-ChildItem -Path $DocsPath -Filter '*.md' -Recurse) + { + $items = Get-ChildItem -Path $DocsPath -Directory -Recurse + foreach ($item in $items) + { + "Updating Markdown help in [$($item.BaseName)]..." + $null = Update-MarkdownHelp -Path $item.FullName -AlphabeticParamsOrder + } + } + + $params = @{ + AlphabeticParamsOrder = $true + ErrorAction = 'SilentlyContinue' + Locale = 'en-US' + Module = $ModuleName + OutputFolder = "$DocsPath\en-US" + WithModulePage = $true + } + + # ErrorAction is set to SilentlyContinue so this + # command will not overwrite an existing Markdown file. + "Creating new Markdown help for [$ModuleName]..." + $null = New-MarkdownHelp @params + } + finally + { + Remove-Module -Name $ModuleName + } +} diff --git a/BuildTasks/ImportDevModule.Task.ps1 b/BuildTasks/ImportDevModule.Task.ps1 new file mode 100644 index 0000000..f4271fc --- /dev/null +++ b/BuildTasks/ImportDevModule.Task.ps1 @@ -0,0 +1,20 @@ + +task ImportDevModule { + + if (-not(Test-Path -Path "$Source\$ModuleName.psd1")) + { + "Module [$ModuleName] is not built; cannot find [$Source\$ModuleName.psd1]." + Write-Error -Message "Could not find module manifest [$Source\$ModuleName.psd1]." + } + else + { + if (Get-Module -Name $ModuleName) + { + "Unloading Module [$ModuleName] from a previous import..." + Remove-Module -Name $ModuleName + } + + "Importing Module [$ModuleName] from [$Source\$ModuleName.psd1]..." + Import-Module -FullyQualifiedName "$Source\$ModuleName.psd1" -Force + } +} diff --git a/BuildTasks/ImportModule.Task.ps1 b/BuildTasks/ImportModule.Task.ps1 new file mode 100644 index 0000000..1017e6b --- /dev/null +++ b/BuildTasks/ImportModule.Task.ps1 @@ -0,0 +1,19 @@ + +task ImportModule { + if (-not(Test-Path -Path $ManifestPath)) + { + "Module [$ModuleName] is not built; cannot find [$ManifestPath]." + Write-Error -Message "Could not find module manifest [$ManifestPath]. You may need to build the module first." + } + else + { + if (Get-Module -Name $ModuleName) + { + "Unloading Module [$ModuleName] from a previous import..." + Remove-Module -Name $ModuleName + } + + "Importing Module [$ModuleName] from [$ManifestPath]..." + Import-Module -FullyQualifiedName $ManifestPath -Force + } +} diff --git a/BuildTasks/Install.Task.ps1 b/BuildTasks/Install.Task.ps1 new file mode 100644 index 0000000..1727f86 --- /dev/null +++ b/BuildTasks/Install.Task.ps1 @@ -0,0 +1,21 @@ + +task Install Uninstall, { + $version = [version] (Get-Metadata -Path $manifestPath -PropertyName 'ModuleVersion') + + $path = $env:PSModulePath.Split(';').Where({ + $_ -like 'C:\Users\*' + }, 'First', 1) + + if ($path -and (Test-Path -Path $path)) + { + "Using [$path] as base path..." + $path = Join-Path -Path $path -ChildPath $ModuleName + $path = Join-Path -Path $path -ChildPath $version + + "Creating directory at [$path]..." + New-Item -Path $path -ItemType 'Directory' -Force -ErrorAction 'Ignore' + + "Copying items from [$Destination] to [$path]..." + Copy-Item -Path "$Destination\*" -Destination $path -Recurse -Force + } +} diff --git a/BuildTasks/InvokeBuildInit.ps1 b/BuildTasks/InvokeBuildInit.ps1 new file mode 100644 index 0000000..1972500 --- /dev/null +++ b/BuildTasks/InvokeBuildInit.ps1 @@ -0,0 +1,9 @@ +$Script:DocsPath = Join-Path -Path $BuildRoot -ChildPath 'Docs' +$Script:Output = Join-Path -Path $BuildRoot -ChildPath 'Output' +$Script:Source = Join-Path -Path $BuildRoot -ChildPath $ModuleName +$Script:Destination = Join-Path -Path $Output -ChildPath $ModuleName +$Script:ManifestPath = Join-Path -Path $Destination -ChildPath "$ModuleName.psd1" +$Script:ModulePath = Join-Path -Path $Destination -ChildPath "$ModuleName.psm1" +$Script:Folders = 'Classes', 'Includes', 'Internal', 'Private', 'Public', 'Resources' +$Script:TestFile = "$BuildRoot\Output\TestResults_PS$PSVersion`_$TimeStamp.xml" +function taskx($Name, $Parameters) { task $Name @Parameters -Source $MyInvocation } diff --git a/BuildTasks/Uninstall.Task.ps1 b/BuildTasks/Uninstall.Task.ps1 new file mode 100644 index 0000000..2bb4346 --- /dev/null +++ b/BuildTasks/Uninstall.Task.ps1 @@ -0,0 +1,28 @@ + +task Uninstall { + 'Unloading Modules...' + Get-Module -Name $ModuleName -ErrorAction 'Ignore' | Remove-Module + + 'Uninstalling Module packages...' + $modules = Get-Module $ModuleName -ErrorAction 'Ignore' -ListAvailable + foreach ($module in $modules) + { + Uninstall-Module -Name $module.Name -RequiredVersion $module.Version -ErrorAction 'Ignore' + } + + 'Cleaning up manually installed Modules...' + $path = $env:PSModulePath.Split(';').Where({ + $_ -like 'C:\Users\*' + }, 'First', 1) + + $path = Join-Path -Path $path -ChildPath $ModuleName + if ($path -and (Test-Path -Path $path)) + { + 'Removing files... (This may fail if any DLLs are in use.)' + Get-ChildItem -Path $path -File -Recurse | + Remove-Item -Force | ForEach-Object 'FullName' + + 'Removing folders... (This may fail if any DLLs are in use.)' + Remove-Item $path -Recurse -Force + } +} diff --git a/BuildTasks/UpdateSource.Task.ps1 b/BuildTasks/UpdateSource.Task.ps1 new file mode 100644 index 0000000..730ab9a --- /dev/null +++ b/BuildTasks/UpdateSource.Task.ps1 @@ -0,0 +1,3 @@ +task UpdateSource { + Copy-Item -Path $ManifestPath -Destination "$Source\$ModuleName.psd1" +}