Create a merged psm1 file when published (#16) !deploy

* Added auto discovery fo files to monitor.
* Fixed scriptanalyzer rule
* Adjusted the parameter attributes
* testing feature specifications
* Added module building
* Exceptions and spacing
This commit is contained in:
Kevin Marquette
2017-05-03 00:08:41 -07:00
committed by GitHub
parent 5cacaa2ebd
commit 3fdc8a02d9
9 changed files with 161 additions and 103 deletions

View File

@@ -5,14 +5,14 @@ class Chronometer
[void]AddBreakpoint([string[]]$Path, [int[]]$LineNumber) [void]AddBreakpoint([string[]]$Path, [int[]]$LineNumber)
{ {
if(-not [string]::IsNullOrEmpty($Path)) if (-not [string]::IsNullOrEmpty($Path))
{ {
foreach($file in (Resolve-Path $Path -ea 0)) foreach ($file in (Resolve-Path $Path -ea 0))
{ {
$script = [MonitoredScript]@{Path=$file.Path} $script = [MonitoredScript]@{Path = $file.Path}
$lines = $script.SetScript($file) $lines = $script.SetScript($file)
if($null -ne $LineNumber) if ( $null -ne $LineNumber )
{ {
$bpLine = $LineNumber $bpLine = $LineNumber
} }
@@ -26,7 +26,7 @@ class Chronometer
$breakpointParam = @{ $breakpointParam = @{
Script = $file Script = $file
Line = $bpLine Line = $bpLine
Action = {[ScriptProfiler]::RecordExecution( $_) } Action = { [ScriptProfiler]::RecordExecution( $_) }
} }
$this.breakPoint += Set-PSBreakpoint @breakpointParam $this.breakPoint += Set-PSBreakpoint @breakpointParam
} }
@@ -35,7 +35,7 @@ class Chronometer
[void]ClearBreakpoint() [void]ClearBreakpoint()
{ {
if($null -ne $this.Breakpoint -and $this.Breakpoint.count -gt 0) if ( $null -ne $this.Breakpoint -and $this.Breakpoint.count -gt 0 )
{ {
Remove-PSBreakpoint -Breakpoint $this.Breakpoint Remove-PSBreakpoint -Breakpoint $this.Breakpoint
} }
@@ -45,7 +45,7 @@ class Chronometer
[void] AddExecution([hashtable]$Execution) [void] AddExecution([hashtable]$Execution)
{ {
$script = $Execution.Breakpoint.Script $script = $Execution.Breakpoint.Script
if($this.FileMap.ContainsKey($script)) if ( $this.FileMap.ContainsKey($script) )
{ {
# Each script tracks it's own execution times # Each script tracks it's own execution times
$this.FileMap[$script].AddExecution($Execution) $this.FileMap[$script].AddExecution($Execution)
@@ -54,7 +54,7 @@ class Chronometer
[MonitoredScript[]] GetResults() [MonitoredScript[]] GetResults()
{ {
foreach($node in $this.FileMap.Values) foreach ( $node in $this.FileMap.Values )
{ {
$node.PostProcessing() $node.PostProcessing()
} }

View File

@@ -17,7 +17,7 @@ class MonitoredScript
[int] SetScript([string]$Path) [int] SetScript([string]$Path)
{ {
$lineNumber = 0 $lineNumber = 0
foreach($command in ( Get-Content -Path $Path )) foreach ( $command in ( Get-Content -Path $Path ) )
{ {
$this.Line.Add( [ScriptLine]::New($command, $path, $lineNumber) ) $this.Line.Add( [ScriptLine]::New($command, $path, $lineNumber) )
$lineNumber++ $lineNumber++
@@ -35,7 +35,7 @@ class MonitoredScript
$record.AddExecution($node) $record.AddExecution($node)
# Calclate the delta in time # Calclate the delta in time
if($this.lastNode) if ( $this.lastNode )
{ {
$duration = $node.Elapsed - $this.lastNode.Elapsed $duration = $node.Elapsed - $this.lastNode.Elapsed
} }
@@ -45,7 +45,7 @@ class MonitoredScript
} }
# The delta is how long the last command ran # The delta is how long the last command ran
if($this.lastRecord) if ( $this.lastRecord )
{ {
$this.lastRecord.AddExecutionTime($duration) $this.lastRecord.AddExecutionTime($duration)
$this.ExecutionTime += $duration $this.ExecutionTime += $duration
@@ -59,15 +59,15 @@ class MonitoredScript
{ {
$this.lastNode = $null $this.lastNode = $null
$this.ExecutionTime = [TimeSpan]::Zero $this.ExecutionTime = [TimeSpan]::Zero
foreach($node in $this.line) foreach ( $node in $this.line )
{ {
$command = $node.text -replace '\s','' $command = $node.text -replace '\s', ''
switch -Regex ($command) switch -Regex ( $command )
{ {
'^}$|^}#|^$' '^}$|^}#|^$'
{ {
if($node.HitCount -eq 0) if ( $node.HitCount -eq 0 )
{ {
$node.HitCount = $this.lastNode.HitCount $node.HitCount = $this.lastNode.HitCount
} }

View File

@@ -33,12 +33,12 @@ class ScriptLine
$this.HitCount += 1 $this.HitCount += 1
$this.Average = $this.Duration.TotalMilliseconds / $this.HitCount $this.Average = $this.Duration.TotalMilliseconds / $this.HitCount
if($Duration -lt $this.Min) if ( $Duration -lt $this.Min )
{ {
$this.Min = $Duration $this.Min = $Duration
} }
if($Duration -gt $this.Max) if ( $Duration -gt $this.Max )
{ {
$this.Max = $Duration $this.Max = $Duration
} }

View File

@@ -1,7 +1,6 @@
function Write-ScriptLine function Write-ScriptLine
{ {
[Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSAvoidUsingWriteHost","")] [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSAvoidUsingWriteHost", "")]
[cmdletbinding()] [cmdletbinding()]
param( param(
[scriptline] [scriptline]
@@ -10,18 +9,18 @@ function Write-ScriptLine
$ErrorAt = [int]::MaxValue $ErrorAt = [int]::MaxValue
) )
if($line) if ( $line )
{ {
$Color = 'Green' $Color = 'Green'
if($line.HitCount -eq 0) if ( $line.HitCount -eq 0 )
{ {
$Color = 'Gray' $Color = 'Gray'
} }
elseif($line.Average -ge $ErrorAt) elseif ( $line.Average -ge $ErrorAt )
{ {
$Color = 'Red' $Color = 'Red'
} }
elseif($line.Average -ge $WarningAt) elseif ( $line.Average -ge $WarningAt )
{ {
$Color = 'Yellow' $Color = 'Yellow'
} }

View File

@@ -1,5 +1,3 @@
function Format-Chronometer function Format-Chronometer
{ {
<# <#
@@ -11,21 +9,21 @@ function Format-Chronometer
$resultes = Get-Chronometer -Path $script.fullname -ScriptBlock {Invoke-Pester C:\workspace\PSGraph} $resultes = Get-Chronometer -Path $script.fullname -ScriptBlock {Invoke-Pester C:\workspace\PSGraph}
$results | Format-Chronometer -WarnAt 20 -ErrorAt 200 $results | Format-Chronometer -WarnAt 20 -ErrorAt 200
#> #>
[Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSAvoidUsingWriteHost","")] [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSAvoidUsingWriteHost", "")]
[cmdletbinding(DefaultParameterSetName='Script')] [cmdletbinding(DefaultParameterSetName = 'Script')]
param( param(
# This is a MonitoredScript object from Get-Chronometer # This is a MonitoredScript object from Get-Chronometer
[Parameter( [Parameter(
ValueFromPipeline=$true, ValueFromPipeline = $true,
ParameterSetName='Script' ParameterSetName = 'Script'
)] )]
[MonitoredScript[]] [MonitoredScript[]]
$InputObject, $InputObject,
# This is a ScriptLine object from a MonitoredScript object # This is a ScriptLine object from a MonitoredScript object
[Parameter( [Parameter(
ValueFromPipeline=$true, ValueFromPipeline = $true,
ParameterSetName='Line' ParameterSetName = 'Line'
)] )]
[ScriptLine[]] [ScriptLine[]]
$Line, $Line,
@@ -45,21 +43,28 @@ function Format-Chronometer
process process
{ {
foreach($script in $InputObject) try
{
if($script.ExecutionTime -ne [TimeSpan]::Zero -or $ShowAll)
{
Write-Host ''
Write-Host "Script: $($script.Path)" -ForegroundColor Green
Write-Host "Execution Time: $($script.ExecutionTime)" -ForegroundColor Green
$script.line | Format-Chronometer -WarningAt $WarningAt -ErrorAt $ErrorAt
}
}
foreach($command in $Line)
{ {
Write-ScriptLine $command -WarningAt $WarningAt -ErrorAt $ErrorAt foreach ( $script in $InputObject )
{
if ( $script.ExecutionTime -ne [TimeSpan]::Zero -or $ShowAll )
{
Write-Host ''
Write-Host "Script: $($script.Path)" -ForegroundColor Green
Write-Host "Execution Time: $($script.ExecutionTime)" -ForegroundColor Green
$script.line | Format-Chronometer -WarningAt $WarningAt -ErrorAt $ErrorAt
}
}
foreach ( $command in $Line )
{
Write-ScriptLine $command -WarningAt $WarningAt -ErrorAt $ErrorAt
}
}
catch
{
$PSCmdlet.ThrowTerminatingError($PSItem)
} }
} }
} }

View File

@@ -23,47 +23,56 @@ function Get-Chronometer
$LineNumber = $null, $LineNumber = $null,
# The script to start the scrupt or execute other commands # The script to start the scrupt or execute other commands
[Parameter(Position=0)] [Parameter(Position = 0)]
[alias('Script','CommandScript')] [alias('Script', 'CommandScript')]
[scriptblock] [scriptblock]
$ScriptBlock $ScriptBlock
) )
if( $null -eq $Path ) process
{ {
$Path = Get-ChildItem -Recurse -Include *.psm1,*.ps1 -File try
}
if($Path.FullName)
{
$Path = $Path.FullName
}
$Chronometer = [Chronometer]::New()
Write-Verbose "Setting breapoints"
$Chronometer.AddBreakpoint($Path,$LineNumber)
if($null -ne $Chronometer.breakPoint -and $null -ne $ScriptBlock)
{
Write-Verbose "Executing Script"
[ScriptProfiler]::Start()
[void] $ScriptBlock.Invoke($Path)
Write-Verbose "Clearing Breapoints"
$Chronometer.ClearBreakpoint()
Write-Verbose "Processing data"
foreach($node in [ScriptProfiler]::Queue.GetEnumerator())
{ {
$Chronometer.AddExecution($node) if ( $null -eq $Path )
} {
$Path = Get-ChildItem -Recurse -Include *.psm1, *.ps1 -File
}
Write-Output $Chronometer.GetResults() if ( $Path.FullName )
} {
else $Path = $Path.FullName
{ }
Write-Warning "Parsing files did not result in any breakpoints"
$Chronometer = [Chronometer]::New()
Write-Verbose "Setting breapoints"
$Chronometer.AddBreakpoint($Path, $LineNumber)
if ( $null -ne $Chronometer.breakPoint -and $null -ne $ScriptBlock )
{
Write-Verbose "Executing Script"
[ScriptProfiler]::Start()
[void] $ScriptBlock.Invoke($Path)
Write-Verbose "Clearing Breapoints"
$Chronometer.ClearBreakpoint()
Write-Verbose "Processing data"
foreach ( $node in [ScriptProfiler]::Queue.GetEnumerator() )
{
$Chronometer.AddExecution($node)
}
Write-Output $Chronometer.GetResults()
}
else
{
Write-Warning "Parsing files did not result in any breakpoints"
}
}
catch
{
$PSCmdlet.ThrowTerminatingError($PSItem)
}
} }
} }

View File

@@ -0,0 +1,13 @@
Given 'We have functions to publish' {
"$psscriptroot\..\chronometer\public\*.ps1" | Should Exist
}
And 'We have a module' {
"$psscriptroot\..\chronometer\chronometer.psd1" | Should Exist
"$psscriptroot\..\chronometer\chronometer.psm1" | Should Exist
}
When 'The user searches for our module' {
Find-Module chronometer | Should Not BeNullOrEmpty
}
Then 'They can install the module' {
{Install-Module chronometer -Scope CurrentUser -WhatIf *>&1} | Should Not Throw
}

View File

@@ -0,0 +1,8 @@
Feature: We need distribute our module to the public
It should be published someplace that is easy to find
Scenario: A user needs to be able to find our module in the PSGallery
Given We have functions to publish
And We have a module
When The user searches for our module
Then They can install the module

View File

@@ -2,11 +2,11 @@
# Init some things # Init some things
Properties { Properties {
# Find the build folder based on build system # Find the build folder based on build system
$ProjectRoot = $ENV:BHProjectPath $ProjectRoot = $ENV:BHProjectPath
if(-not $ProjectRoot) if (-not $ProjectRoot)
{ {
$ProjectRoot = $PSScriptRoot $ProjectRoot = $PSScriptRoot
} }
$Timestamp = Get-date -uformat "%Y%m%d-%H%M%S" $Timestamp = Get-date -uformat "%Y%m%d-%H%M%S"
$PSVersion = $PSVersionTable.PSVersion.Major $PSVersion = $PSVersionTable.PSVersion.Major
@@ -14,7 +14,7 @@ Properties {
$lines = '----------------------------------------------------------------------' $lines = '----------------------------------------------------------------------'
$Verbose = @{} $Verbose = @{}
if($ENV:BHCommitMessage -match "!verbose") if ( $ENV:BHCommitMessage -match "!verbose" )
{ {
$Verbose = @{Verbose = $True} $Verbose = @{Verbose = $True}
} }
@@ -35,14 +35,14 @@ Task UnitTests -Depends Init {
'Running quick unit tests to fail early if there is an error' 'Running quick unit tests to fail early if there is an error'
$TestResults = Invoke-Pester -Path $ProjectRoot\Tests\*unit* -PassThru -Tag Build $TestResults = Invoke-Pester -Path $ProjectRoot\Tests\*unit* -PassThru -Tag Build
if($TestResults.FailedCount -gt 0) if ( $TestResults.FailedCount -gt 0 )
{ {
Write-Error "Failed '$($TestResults.FailedCount)' tests, build failed" Write-Error "Failed '$($TestResults.FailedCount)' tests, build failed"
} }
"`n" "`n"
} }
Task Test -Depends UnitTests { Task Test -Depends UnitTests {
$lines $lines
"`n`tSTATUS: Testing with PowerShell $PSVersion" "`n`tSTATUS: Testing with PowerShell $PSVersion"
@@ -50,11 +50,11 @@ Task Test -Depends UnitTests {
$TestResults = Invoke-Pester -Path $ProjectRoot\Tests -PassThru -OutputFormat NUnitXml -OutputFile "$ProjectRoot\$TestFile" -Tag Build $TestResults = Invoke-Pester -Path $ProjectRoot\Tests -PassThru -OutputFormat NUnitXml -OutputFile "$ProjectRoot\$TestFile" -Tag Build
# In Appveyor? Upload our tests! #Abstract this into a function? # In Appveyor? Upload our tests! #Abstract this into a function?
If($ENV:BHBuildSystem -eq 'AppVeyor') If ( $ENV:BHBuildSystem -eq 'AppVeyor' )
{ {
[xml]$content = Get-Content "$ProjectRoot\$TestFile" [xml]$content = Get-Content "$ProjectRoot\$TestFile"
$content.'test-results'.'test-suite'.type = "Powershell" $content.'test-results'.'test-suite'.type = "Powershell"
$content.Save("$ProjectRoot\$TestFile") $content.Save( "$ProjectRoot\$TestFile" )
"Uploading $ProjectRoot\$TestFile to AppVeyor" "Uploading $ProjectRoot\$TestFile to AppVeyor"
"JobID: $env:APPVEYOR_JOB_ID" "JobID: $env:APPVEYOR_JOB_ID"
@@ -65,7 +65,7 @@ Task Test -Depends UnitTests {
# Failed tests? # Failed tests?
# Need to tell psake or it will proceed to the deployment. Danger! # Need to tell psake or it will proceed to the deployment. Danger!
if($TestResults.FailedCount -gt 0) if ( $TestResults.FailedCount -gt 0 )
{ {
Write-Error "Failed '$($TestResults.FailedCount)' tests, build failed" Write-Error "Failed '$($TestResults.FailedCount)' tests, build failed"
} }
@@ -76,8 +76,8 @@ Task Build -Depends Test {
$lines $lines
$functions = Get-ChildItem "$PSScriptRoot\$env:BHProjectName\Public\*.ps1" | $functions = Get-ChildItem "$PSScriptRoot\$env:BHProjectName\Public\*.ps1" |
Where-Object{ $_.name -notmatch 'Tests'} | Where-Object { $_.name -notmatch 'Tests'} |
Select-Object -ExpandProperty basename Select-Object -ExpandProperty basename
# Load the module, read the exported functions, update the psd1 FunctionsToExport # Load the module, read the exported functions, update the psd1 FunctionsToExport
Set-ModuleFunctions -Name $env:BHPSModuleManifest -FunctionsToExport $functions Set-ModuleFunctions -Name $env:BHPSModuleManifest -FunctionsToExport $functions
@@ -85,23 +85,47 @@ Task Build -Depends Test {
# Bump the module version # Bump the module version
$version = [version] (Step-Version (Get-Metadata -Path $env:BHPSModuleManifest)) $version = [version] (Step-Version (Get-Metadata -Path $env:BHPSModuleManifest))
$galleryVersion = Get-NextPSGalleryVersion -Name $env:BHProjectName $galleryVersion = Get-NextPSGalleryVersion -Name $env:BHProjectName
if($version -lt $galleryVersion) if ( $version -lt $galleryVersion )
{ {
$version = $galleryVersion $version = $galleryVersion
} }
$version = [version]::New($version.Major,$version.Minor,$version.Build,$env:BHBuildNumber) $version = [version]::New($version.Major, $version.Minor, $version.Build, $env:BHBuildNumber)
Write-Host "Using version: $version" Write-Host "Using version: $version"
Update-Metadata -Path $env:BHPSModuleManifest -PropertyName ModuleVersion -Value $version Update-Metadata -Path $env:BHPSModuleManifest -PropertyName ModuleVersion -Value $version
$psm1 = "$PSScriptRoot\$env:BHProjectName\$env:BHProjectName.psm1"
# keep the first line in the psm1 file and clear the rest
( Get-Content -Path $psm1 -TotalCount 1 ) | Set-Content -Path $psm1
foreach ( $folder in ('Classes', 'Public', 'Private') )
{
Add-Content -Path $psm1 -Value "Write-Verbose 'Importing from [$folder]'"
$imports = Get-ChildItem "$PSScriptRoot\$env:BHProjectName\$folder\*.ps1" -Exclude *.Tests.ps1
foreach ( $file in $imports )
{
Add-Content -Path $psm1 -Value ''
Add-Content -Path $psm1 -Value "Write-Verbose ' Source [\$folder\$($file.name)]'"
[System.IO.File]::ReadAllText($file.fullname) | Add-Content -Path $psm1
}
# Remove folders after import
Remove-Item "$PSScriptRoot\$env:BHProjectName\$folder" -Recurse
}
# Add exports
Add-Content -Path $psm1 -Value "Export-ModuleMember -Function $($functions -join ', ')"
Import-Module $psm1 -Force
} }
Task Deploy -Depends Build { Task Deploy -Depends Build {
$lines $lines
# Gate deployment # Gate deployment
if( if (
$ENV:BHBuildSystem -ne 'Unknown' -and $ENV:BHBuildSystem -ne 'Unknown' -and
$ENV:BHBranchName -eq "master" -and $ENV:BHBranchName -eq "master" -and
$ENV:BHCommitMessage -match '!deploy' $ENV:BHCommitMessage -match '!deploy'
) )
{ {
@@ -114,9 +138,9 @@ Task Deploy -Depends Build {
} }
else else
{ {
"Skipping deployment: To deploy, ensure that...`n" + "Skipping deployment: To deploy, ensure that...`n" +
"`t* You are in a known build system (Current: $ENV:BHBuildSystem)`n" + "`t* You are in a known build system (Current: $ENV:BHBuildSystem)`n" +
"`t* You are committing to the master branch (Current: $ENV:BHBranchName) `n" + "`t* You are committing to the master branch (Current: $ENV:BHBranchName) `n" +
"`t* Your commit message includes !deploy (Current: $ENV:BHCommitMessage)" "`t* Your commit message includes !deploy (Current: $ENV:BHCommitMessage)"
} }
} }