diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 0000000..94cb4da --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,97 @@ +name: CI + +on: + push: {} + pull_request: + branches: [ main ] + +jobs: + build: + runs-on: ubuntu-latest + env: + FORCE_COLOR: 1 + steps: + - uses: earthly/actions-setup@v1 + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + + - uses: actions/checkout@v4 + with: + submodules: true + fetch-depth: 0 + + - name: earthly +test + if: github.ref != 'refs/heads/main' + run: earthly --strict +test + + - name: earthly +push + if: github.ref == 'refs/heads/main' + run: earthly --push --secret NUGET_API_KEY --secret PSGALLERY_API_KEY --strict +all + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + NUGET_API_KEY: ${{ secrets.NUGET_API_KEY }} + PSGALLERY_API_KEY: ${{ secrets.PSGALLERY_API_KEY }} + + - uses: actions/upload-artifact@v4 + with: + name: ModuleBuilder + path: Modules/ModuleBuilder + + - uses: actions/upload-artifact@v4 + with: + name: TestResults + path: Modules/ModuleBuilder-TestResults + + - uses: actions/upload-artifact@v4 + with: + name: Packages + path: Modules/ModuleBuilder-Packages + + - name: Upload Tests + uses: actions/upload-artifact@v4 + with: + name: PesterTests + path: ${{github.workspace}}/Tests + - name: Upload RequiredModules.psd1 + uses: actions/upload-artifact@v4 + with: + name: RequiredModules + path: ${{github.workspace}}/RequiredModules.psd1 + test: + needs: build + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + os: [windows-latest, ubuntu-latest, macos-latest] + steps: + - name: Download Build Output + uses: actions/download-artifact@v4 + with: + name: ModuleBuilder + path: Modules/ModuleBuilder + - name: Download Pester Tests + uses: actions/download-artifact@v4 + with: + name: PesterTests + path: PesterTests + - name: Download RequiredModules + uses: actions/download-artifact@v4 + with: + name: RequiredModules + + - uses: PoshCode/Actions/install-requiredmodules@v1 + - uses: PoshCode/Actions/pester@v1 + with: + codeCoveragePath: Modules/ModuleBuilder + moduleUnderTest: ModuleBuilder + additionalModulePaths: ${{github.workspace}}/Modules + - name: Publish Test Results + uses: zyborg/dotnet-tests-report@v1 + with: + test_results_path: results.xml + - name: Upload Results + uses: actions/upload-artifact@v2 + with: + name: Pester Results + path: ${{github.workspace}}/*.xml diff --git a/.gitignore b/.gitignore index ea1472e..f5e556f 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ output/ +Modules/ diff --git a/Build.build.ps1 b/Build.build.ps1 index 1606b90..5fbe9f6 100644 --- a/Build.build.ps1 +++ b/Build.build.ps1 @@ -4,28 +4,42 @@ .EXAMPLE Invoke-Build .NOTES - 0.5.0 - Parameterize - Add parameters to this script to control the build + 0.7.0 - Now works with Earthly + 0.6.0 - Add PesterFilter for the Pester tass + 0.5.0 - Add Parameters to control the build (Clean, CollectCoverage) + These are actually used by the Invoke-Build Tasks #> [CmdletBinding()] param( - # dotnet build configuration parameter (Debug or Release) - [ValidateSet('Debug', 'Release')] - [string]$Configuration = 'Release', - # Add the clean task before the default build [switch]$Clean, # Collect code coverage when tests are run - [switch]$CollectCoverage + [switch]$CollectCoverage, + + # The PesterFilter from New-PesterConfiguration. + # Supports specifying any of: + # Tag: Tags of Describe, Context or It to be run. + # ExcludeTag: Tags of Describe, Context or It to be excluded from the run. + # Line: Filter by file and scriptblock start line, useful to run parsed tests programmatically to avoid problems with expanded names. Example: 'C:\tests\file1.Tests.ps1:37' + # ExcludeLine: Exclude by file and scriptblock start line, takes precedence over Line. + # FullName: Full name of test with -like wildcards, joined by dot. Example: '*.describe Get-Item.test1' + [hashtable]$PesterFilter ) $InformationPreference = "Continue" +$ErrorView = 'DetailedView' -$BuildTasks = "BuildTasks", "../BuildTasks", "../../BuildTasks" | Convert-Path -ErrorAction Ignore | Select-Object -First 1 +# The name of the module to publish +$script:PSModuleName = "TerminalBlocks" +$script:RequiredCodeCoverage = 0.85 +# Use Env because then Earthly can override it +$Env:OUTPUT_ROOT ??= Join-Path $BuildRoot Modules +$Tasks = "Tasks", "../Tasks", "../../Tasks" | Convert-Path -ErrorAction Ignore | Select-Object -First 1 +Write-Information "$($PSStyle.Foreground.BrightCyan)Found shared tasks in $Tasks" -Tag "InvokeBuild" ## Self-contained build script - can be invoked directly or via Invoke-Build if ($MyInvocation.ScriptName -notlike '*Invoke-Build.ps1') { - & "$BuildTasks/_Bootstrap.ps1" + & "$Tasks/_Bootstrap.ps1" Invoke-Build -File $MyInvocation.MyCommand.Path @PSBoundParameters -Result Result @@ -36,12 +50,12 @@ if ($MyInvocation.ScriptName -notlike '*Invoke-Build.ps1') { exit 0 } -## The first task defined is the default task -if ($Clean) { - Add-BuildTask . Clean, Test -} else { - Add-BuildTask . Test +## The first task defined is the default task, +if ($dotnetProjects -and $Clean) { + Add-BuildTask CleanBuild Clean, ($Task ?? "Test") +} elseif ($Clean) { + Add-BuildTask CleanBuild Clean, ($Task ?? "Test") } -## Initialize the build variables, and import shared tasks, including DotNet tasks -. "$BuildTasks/_Initialize.ps1" -PSModuleTasks \ No newline at end of file +## Initialize the build variables, and import shared tasks +. "$Tasks/_Initialize.ps1" diff --git a/Earthfile b/Earthfile new file mode 100644 index 0000000..a166ad5 --- /dev/null +++ b/Earthfile @@ -0,0 +1,82 @@ +VERSION 0.7 +IMPORT github.com/poshcode/tasks +FROM mcr.microsoft.com/dotnet/sdk:8.0 +WORKDIR /work + +ARG --global EARTHLY_GIT_ORIGIN_URL +ARG --global EARTHLY_BUILD_SHA +ARG --global EARTHLY_GIT_BRANCH +# These are my common paths, used in my shared /Tasks repo +ARG --global OUTPUT_ROOT=/Modules +ARG --global TEST_ROOT=/Tests +ARG --global TEMP_ROOT=/temp +# These are my common build args, used in my shared /Tasks repo +ARG --global MODULE_NAME=ErrorView +ARG --global CONFIGURATION=Release + +# This works on Linux, and speeds things up dramatically because I don't need my .git folder +# But "LOCALLY" doesn't work on Windows (yet), and that's my main dev environment +# version: +# COPY --if-exists GitVersion.yml . +# IF [ -f ./GitVersion.yml ] +# ELSE +# LOCALLY +# COPY tasks+tasks/tasks/GitVersion.yml . +# END +# LOCALLY +# RUN dotnet tool update GitVersion.Tool --version 5.12.0 --global && \ +# dotnet gitversion > version.json +# SAVE ARTIFACT ./version.json + +worker: + # Dotnet tools and scripts installed by PSGet + ENV PATH=$HOME/.dotnet/tools:$HOME/.local/share/powershell/Scripts:$PATH + RUN mkdir /Tasks \ + && git config --global user.email "Jaykul@HuddledMasses.org" \ + && git config --global user.name "Earthly Build" + # I'm using Invoke-Build tasks from this other repo which rarely changes + COPY tasks+tasks/* /Tasks + # Dealing with dependencies first allows docker to cache packages for us + # So the dependency cach only re-builds when you add a new dependency + COPY RequiredModules.psd1 . + # COPY --if-exists *.csproj . + RUN ["pwsh", "-File", "/Tasks/_Bootstrap.ps1", "-RequiredModulesPath", "RequiredModules.psd1"] + +build: + FROM +worker + RUN mkdir $OUTPUT_ROOT $TEST_ROOT $TEMP_ROOT + # On Linux we could use the version: task, we wouldn't need .git/ here and + # we could avoid re-running this every time there was a commit + # we could copying the source and Invoke-Build script to improve caching + COPY --if-exists --dir .git/ source/ build.psd1 Build.build.ps1 GitVersion.yml nuget.config /work + RUN ["pwsh", "-Command", "Invoke-Build", "-Task", "Build", "-File", "Build.build.ps1"] + + # SAVE ARTIFACT [--keep-ts] [--keep-own] [--if-exists] [--force] [] [AS LOCAL ] + SAVE ARTIFACT $OUTPUT_ROOT/$MODULE_NAME AS LOCAL ./Modules/$MODULE_NAME + +test: + # If we run a target as a reference in FROM or COPY, it's outputs will not be produced + # BUILD +build + FROM +build + # Copy the test files here, so we can avoid rebuilding when iterating on tests + COPY --if-exists --dir Tests/ ScriptAnalyzerSettings.psd1 /work + RUN ["pwsh", "-Command", "Invoke-Build", "-Task", "Test", "-File", "Build.build.ps1"] + + # SAVE ARTIFACT [--keep-ts] [--keep-own] [--if-exists] [--force] [] [AS LOCAL ] + SAVE ARTIFACT $TEST_ROOT AS LOCAL ./Modules/$MODULE_NAME-TestResults + +# pack: +# BUILD +test # So that we get the module artifact from build too +# FROM +test +# RUN ["pwsh", "-Command", "Invoke-Build", "-Task", "Pack", "-File", "Build.build.ps1", "-Verbose"] +# SAVE ARTIFACT $OUTPUT_ROOT/publish/*.nupkg AS LOCAL ./Modules/$MODULE_NAME-Packages/ + +push: + FROM +build + RUN --push --secret NUGET_API_KEY --secret PSGALLERY_API_KEY -- \ + pwsh -Command Invoke-Build -Task Push -File Build.build.ps1 -Verbose + +all: + # BUILD +build + BUILD +test + BUILD +push diff --git a/RequiredModules.psd1 b/RequiredModules.psd1 new file mode 100644 index 0000000..48b2068 --- /dev/null +++ b/RequiredModules.psd1 @@ -0,0 +1,10 @@ +# NOTE: follow nuget syntax for versions: https://docs.microsoft.com/en-us/nuget/reference/package-versioning#version-ranges-and-wildcards +@{ + Configuration = "[1.5.0, 2.0)" + Metadata = "[1.5.1, 2.0)" + Pester = "[5.6.1, 6.0)" + ModuleBuilder = "[3.0.0, 4.0)" + PSScriptAnalyzer = "[1.21.0,2.0)" + PowerShellGet = "2.0.4" + InvokeBuild = "[5.10.4,6.0)" +} diff --git a/build.psd1 b/build.psd1 index 401a540..a3d17d5 100644 --- a/build.psd1 +++ b/build.psd1 @@ -1,5 +1,5 @@ @{ - ModuleManifest = ".\Source\ErrorView.psd1" + ModuleManifest = "./source/ErrorView.psd1" CopyPaths = 'ErrorView.format.ps1xml' Prefix = 'prefix.ps1' # The rest of the paths are relative to the manifest diff --git a/source/ErrorView.psd1 b/source/ErrorView.psd1 index ce3dfdd..65ed3a7 100644 --- a/source/ErrorView.psd1 +++ b/source/ErrorView.psd1 @@ -1,11 +1,11 @@ @{ - Description = 'Enhances formatting ability for Errors' + Description = 'Enhances formatting of Errors, and provides compatibility for older versions of PowerShell' GUID = '5857f85c-8a0a-44e1-8da5-c8ff352167e0' Author = 'Joel Bennett' CompanyName = 'PoshCode' ModuleToProcess = 'ErrorView.psm1' - ModuleVersion = '0.0.2' + ModuleVersion = '0.0.3' Copyright = '(c) Joel Bennett. All rights reserved.' @@ -30,7 +30,7 @@ # IconUri = '' # ReleaseNotes of this module - ReleaseNotes = 'I wrote this module to enhace ErrorViews for Windows PowerShell (without waiting for PS7+)' + ReleaseNotes = 'I wrote this module to enhance ErrorViews for PowerShell (without waiting for PS7+)' } # End of PSData hashtable diff --git a/tests/Compatibility.Tests.ps1 b/tests/Compatibility.Tests.ps1 new file mode 100644 index 0000000..e8c4118 --- /dev/null +++ b/tests/Compatibility.Tests.ps1 @@ -0,0 +1,62 @@ +#requires -Module Pansies +Describe "Format-Error produces the same results" { + BeforeAll { + $_cacheErrorView = $global:ErrorView + try { + Invoke-Expression '$R = "$([char]27)]8;;{0}`a{0}$([char]27)]8;;`a" -f $pwd, Split-Path -Leaf $pwd' + } catch { } + $TestError = $Error[0] + + # Try to clear our ErrorView format data + Remove-Item "$PSScriptRoot/../output/ErrorView/ErrorView.backup" -ErrorAction Ignore + Rename-Item "$PSScriptRoot/../output/ErrorView/ErrorView.format.ps1xml" "ErrorView.backup" + Set-Content "$PSScriptRoot/../output/ErrorView/ErrorView.format.ps1xml" ( + '' + "`n" + + '') + Remove-Module ErrorView -ErrorAction SilentlyContinue + Update-FormatData + + + [System.Management.Automation.ErrorView]$global:ErrorView = 'ConciseView' + $ExpectedConciseView = $TestError | Out-String + Write-Host "$($PSStyle.Foreground.Red)ExpectedConciseView: " $PSStyle.Reset $ExpectedConciseView + + [System.Management.Automation.ErrorView]$global:ErrorView = 'NormalView' + $ExpectedNormalView = $TestError | Out-String + Write-Host "$($PSStyle.Foreground.Red)ExpectedNormalView: " $PSStyle.Reset $ExpectedNormalView + + [System.Management.Automation.ErrorView]$global:ErrorView = 'CategoryView' + $ExpectedCategoryView = $TestError | Out-String + Write-Host "$($PSStyle.Foreground.Red)ExpectedCategoryView: " $PSStyle.Reset $ExpectedCategoryView + + [System.Management.Automation.ErrorView]$global:ErrorView = 'DetailedView' + $ExpectedDetailedView = $TestError | Out-String + # Write-Host "$($PSStyle.Foreground.Red)ExpectedDetailedView: " $PSStyle.Reset $ExpectedDetailedView + + Remove-Item "$PSScriptRoot/../output/ErrorView/ErrorView.format.ps1xml" -ErrorAction Ignore + Rename-Item "$PSScriptRoot/../output/ErrorView/ErrorView.backup" "ErrorView.format.ps1xml" + Import-Module $PSScriptRoot/../output/ErrorView/ErrorView.psd1 -Force + + } + AfterAll { + $global:ErrorView = $_cacheErrorView + } + + It 'As the default CategoryView' { + $actual = $TestError | Format-Error -View CategoryView | Out-String + $actual | Should -Be $ExpectedCategoryView + } + It 'As the default ConciseView' { + $actual = $TestError | Format-Error -View ConciseView | Out-String + $actual | Should -Be $ExpectedConciseView + } + It 'As the default NormalView' { + $actual = $TestError | Format-Error -View NormalView | Out-String + $actual | Should -Be $ExpectedNormalView + } + It 'As the default DetailedView' { + $actual = $TestError | Format-Error -View DetailedView | Out-String + $actual | Should -Be $ExpectedDetailedView + } + +} \ No newline at end of file diff --git a/tests/WrapString.Tests.ps1 b/tests/WrapString.Tests.ps1 new file mode 100644 index 0000000..7959066 --- /dev/null +++ b/tests/WrapString.Tests.ps1 @@ -0,0 +1,17 @@ +#requires -Module Pansies +Describe WrapString { + BeforeAll { + $CommandUnderTest = & (Get-Module ErrorView) { Get-Command WrapString } + } + + It "Word-wraps text to keep it under a specified width" { + "The quick brown fox jumped over the lazy dog and then ran away with the unicorn." | + & $CommandUnderTest -Width 20 <# -Verbose #> | + Should -Be "The quick brown fox", "jumped over the lazy", "dog and then ran", "away with the", "unicorn." + } + It "Does not count ANSI escape sequences as characters" { + "The quick brown ${fg:red}fox${fg:clear} jumped over the lazy ${fg:green}dog and then ran away with the unicorn.${fg:clear}" | + & $CommandUnderTest -Width 20 <# -Verbose #> | + Should -Be "The quick brown ${fg:red}fox${fg:clear}", "jumped over the lazy", "${fg:green}dog and then ran", "away with the", "unicorn.${fg:clear}" + } +}