diff --git a/.github/dependabot.yml b/.github/dependabot.yml index f57e1e9..53188fe 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -7,5 +7,8 @@ version: 2 updates: - package-ecosystem: github-actions # See documentation for possible values directory: / # Location of package manifests + labels: + - dependencies + - github-actions schedule: interval: weekly diff --git a/.github/workflows/Action-Test.yml b/.github/workflows/Action-Test.yml index afa6647..664004f 100644 --- a/.github/workflows/Action-Test.yml +++ b/.github/workflows/Action-Test.yml @@ -22,18 +22,13 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout repo - uses: actions/checkout@v4 - - - name: Initialize environment - uses: PSModule/Initialize-PSModule@main + uses: actions/checkout@v6 - name: Action-Test uses: ./ env: GITHUB_TOKEN: ${{ github.token }} with: - Verbose: true - Debug: true Name: PSModuleTest WorkingDirectory: tests APIKey: ${{ secrets.APIKEY }} diff --git a/.github/workflows/Auto-Release.yml b/.github/workflows/Auto-Release.yml index 680da5c..976b40c 100644 --- a/.github/workflows/Auto-Release.yml +++ b/.github/workflows/Auto-Release.yml @@ -26,7 +26,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout Code - uses: actions/checkout@v4 + uses: actions/checkout@v6 - name: Auto-Release uses: PSModule/Auto-Release@v1 diff --git a/.github/workflows/Linter.yml b/.github/workflows/Linter.yml index 1f677cb..1962629 100644 --- a/.github/workflows/Linter.yml +++ b/.github/workflows/Linter.yml @@ -19,7 +19,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout repo - uses: actions/checkout@v4 + uses: actions/checkout@v6 with: fetch-depth: 0 diff --git a/README.md b/README.md index 83faa3c..c48bccc 100644 --- a/README.md +++ b/README.md @@ -1,105 +1,3 @@ # Publish-PSModule -Creates a GitHub release and publishes the PowerShell module to the PowerShell Gallery. - -This GitHub Action is a part of the [PSModule framework](https://github.com/PSModule). It is recommended to use the [Process-PSModule workflow](https://github.com/PSModule/Process-PSModule) to automate the whole process of managing the PowerShell module. - -## Specifications and practices - -Publish-PSModule follows: - -- [SemVer 2.0.0 specifications](https://semver.org) -- [GitHub Flow specifications](https://docs.github.com/en/get-started/using-github/github-flow) -- [Continiuous Delivery practices](https://en.wikipedia.org/wiki/Continuous_delivery) - -... and supports the following practices in the PSModule framework: - -- [PowerShell publishing guidelines](https://learn.microsoft.com/en-us/powershell/gallery/concepts/publishing-guidelines?view=powershellget-3.x) - -## How it works - -The workflow will trigger on pull requests to the repositorys default branch. -When the pull request is opened, the action will decide what to do based on labels on the pull request. - -It will get the latest release version by looking up the versions in GitHub releases, PowerShell Gallery and the module manifest. -The next version is then determined by the labels on the pull request. If a prerelease label is found, the action will create a -prerelease with the branch name (in normalized form) as the prerelease name. By defualt, the following labels are used: - -- For a major release, and increasing the first number in the version use: - - `major` - - `breaking` -- For a minor release, and increasing the second number in the version. - - `minor` - - `feature` -- For a patch release, and increases the third number in the version. - - `patch` - - `fix` - -The types of labels used for the types of prereleases can be configured using the `MajorLabels`, `MinorLabels` and `PatchLabels` -parameters/settings. See the [Usage](#usage) section for more information. - -When a pull request is merged into the default branch, the action will create a release based on the labels and clean up any previous -prereleases that was created. - -## Usage - -The action can be configured using the following settings: - -| Name | Description | Required | Default | -| --- | --- | --- | --- | -| `Name` | Name of the module to publish. Defaults to the repository name. | `false` | | -| `ModulePath` | Path to the folder where the module to publish is located. | `false` | `outputs/modules` | -| `APIKey` | PowerShell Gallery API Key. | `true` | | -| `AutoCleanup`| Control wether to automatically cleanup prereleases. If disabled, the action will not remove any prereleases. | `false` | `true` | -| `AutoPatching` | Control wether to automatically handle patches. If disabled, the action will only create a patch release if the pull request has a 'patch' label. | `false` | `true` | -| `DatePrereleaseFormat` | The format to use for the prerelease number using [.NET DateTime format strings](https://learn.microsoft.com/en-us/dotnet/standard/base-types/standard-date-and-time-format-strings). | `false` | `''` | -| `IncrementalPrerelease` | Control wether to automatically increment the prerelease number. If disabled, the action will ensure only one prerelease exists for a given branch. | `false` | `true` | -| `VersionPrefix` | The prefix to use for the version number. | `false` | `v` | -| `MajorLabels` | A comma separated list of labels that trigger a major release. | `false` | `major, breaking` | -| `MinorLabels` | A comma separated list of labels that trigger a minor release. | `false` | `minor, feature` | -| `PatchLabels` | A comma separated list of labels that trigger a patch release. | `false` | `patch, fix` | -| `IgnoreLabels` | A comma separated list of labels that do not trigger a release. | `false` | `NoRelease` | -| `WhatIf` | Control wether to simulate the action. If enabled, the action will not create any releases. Used for testing. | `false` | `false` | -| `Debug` | Enable debug output. | `'false'` | `false` | -| `Verbose` | Enable verbose output. | `'false'` | `false` | -| `Version` | Specifies the version of the GitHub module to be installed. The value must be an exact version. | | `false` | -| `Prerelease` | Allow prerelease versions if available. | `'false'` | `false` | -| `WorkingDirectory` | The working directory where the script runs. | `'false'` | `'.'` | - -## Example - -```yaml -name: Publish-PSModule - -on: [pull_request] - -jobs: - Publish-PSModule: - name: Publish-PSModule - runs-on: ubuntu-latest - steps: - - name: Checkout repo - uses: actions/checkout@v4 - - - name: Initialize environment - uses: PSModule/Initialize-PSModule@main - - - name: Publish-PSModule - uses: PSModule/Publish-PSModule@main - env: - GITHUB_TOKEN: ${{ github.token }} - with: - APIKey: ${{ secrets.APIKEY }} -``` - -## Permissions - -The action requires the following permissions: - -If running the action in a restrictive mode, the following permissions needs to be granted to the action: - -```yaml -permissions: - contents: write # Required to create releases - pull-requests: write # Required to create comments on the PRs -``` +This GitHub Action is a part of the [PSModule framework](https://github.com/PSModule). diff --git a/action.yml b/action.yml index 2b0fac0..654f6b3 100644 --- a/action.yml +++ b/action.yml @@ -1,9 +1,6 @@ -name: Publish-PSModule (by PSModule) +name: Publish-PSModule description: Publish a PowerShell module to the PowerShell Gallery. author: PSModule -branding: - icon: upload-cloud - color: gray-dark inputs: Name: @@ -56,21 +53,6 @@ inputs: description: If specified, the action will only log the changes it would make, but will not actually create or delete any releases or tags. required: false default: 'false' - Debug: - description: Enable debug output. - required: false - default: 'false' - Verbose: - description: Enable verbose output. - required: false - default: 'false' - Version: - description: Specifies the version of the GitHub module to be installed. The value must be an exact version. - required: false - Prerelease: - description: Allow prerelease versions if available. - required: false - default: 'false' WorkingDirectory: description: The working directory where the script will run from. required: false @@ -83,7 +65,8 @@ runs: uses: PSModule/Install-PSModuleHelpers@v1 - name: Run Publish-PSModule - uses: PSModule/GitHub-Script@v1 + shell: pwsh + working-directory: ${{ inputs.WorkingDirectory }} env: PSMODULE_PUBLISH_PSMODULE_INPUT_Name: ${{ inputs.Name }} PSMODULE_PUBLISH_PSMODULE_INPUT_ModulePath: ${{ inputs.ModulePath }} @@ -98,12 +81,4 @@ runs: PSMODULE_PUBLISH_PSMODULE_INPUT_PatchLabels: ${{ inputs.PatchLabels }} PSMODULE_PUBLISH_PSMODULE_INPUT_VersionPrefix: ${{ inputs.VersionPrefix }} PSMODULE_PUBLISH_PSMODULE_INPUT_WhatIf: ${{ inputs.WhatIf }} - PSMODULE_PUBLISH_PSMODULE_INPUT_WorkingDirectory: ${{ inputs.WorkingDirectory }} - with: - Name: Publish-PSModule - Debug: ${{ inputs.Debug }} - Prerelease: ${{ inputs.Prerelease }} - Verbose: ${{ inputs.Verbose }} - Version: ${{ inputs.Version }} - WorkingDirectory: ${{ inputs.WorkingDirectory }} - Script: ${{ github.action_path }}/scripts/main.ps1 + run: ${{ github.action_path }}/scripts/main.ps1 diff --git a/scripts/helpers/Publish-PSModule.ps1 b/scripts/helpers/Publish-PSModule.ps1 index 96ce20d..956ffc1 100644 --- a/scripts/helpers/Publish-PSModule.ps1 +++ b/scripts/helpers/Publish-PSModule.ps1 @@ -1,17 +1,16 @@ function Publish-PSModule { <# - .SYNOPSIS - Publishes a module to the PowerShell Gallery and GitHub Pages. + .SYNOPSIS + Publishes a module to the PowerShell Gallery and creates a GitHub Release. - .DESCRIPTION - Publishes a module to the PowerShell Gallery and GitHub Pages. + .DESCRIPTION + Publishes a module to the PowerShell Gallery and creates a GitHub Release. - .EXAMPLE - Publish-PSModule -Name 'PSModule.FX' -APIKey $env:PSGALLERY_API_KEY + .EXAMPLE + Publish-PSModule -Name 'PSModule.FX' -APIKey $env:PSGALLERY_API_KEY #> [OutputType([void])] [CmdletBinding()] - #Requires -Modules Utilities, PowerShellGet, Microsoft.PowerShell.PSResourceGet, GitHub, PSSemVer [Diagnostics.CodeAnalysis.SuppressMessageAttribute( 'PSReviewUnusedParameter', '', Scope = 'Function', Justification = 'LogGroup - Scoping affects the variables line of sight.' @@ -20,6 +19,10 @@ 'PSUseDeclaredVarsMoreThanAssignments', '', Justification = 'LogGroup - Scoping affects the variables line of sight.' )] + [Diagnostics.CodeAnalysis.SuppressMessageAttribute( + 'PSAvoidUsingWriteHost', '', + Justification = 'Log outputs to GitHub Actions logs.' + )] param( # Name of the module to process. [Parameter()] @@ -34,7 +37,7 @@ [string] $APIKey ) - LogGroup 'Set configuration' { + Set-GitHubLogGroup 'Set configuration' { $autoCleanup = $env:PSMODULE_PUBLISH_PSMODULE_INPUT_AutoCleanup -eq 'true' $autoPatching = $env:PSMODULE_PUBLISH_PSMODULE_INPUT_AutoPatching -eq 'true' $incrementalPrerelease = $env:PSMODULE_PUBLISH_PSMODULE_INPUT_IncrementalPrerelease -eq 'true' @@ -60,18 +63,18 @@ } | Format-List | Out-String } - LogGroup 'Event information - JSON' { + Set-GitHubLogGroup 'Event information - JSON' { $githubEventJson = Get-Content $env:GITHUB_EVENT_PATH $githubEventJson | Format-List | Out-String } - LogGroup 'Event information - Object' { + Set-GitHubLogGroup 'Event information - Object' { $githubEvent = $githubEventJson | ConvertFrom-Json $pull_request = $githubEvent.pull_request $githubEvent | Format-List | Out-String } - LogGroup 'Event information - Details' { + Set-GitHubLogGroup 'Event information - Details' { $defaultBranchName = (gh repo view --json defaultBranchRef | ConvertFrom-Json | Select-Object -ExpandProperty defaultBranchRef).name $isPullRequest = $githubEvent.PSObject.Properties.Name -Contains 'pull_request' if (-not ($isPullRequest -or $whatIf)) { @@ -97,17 +100,17 @@ Write-Output '-------------------------------------------------' } - LogGroup 'Pull request - details' { + Set-GitHubLogGroup 'Pull request - details' { $pull_request | Format-List | Out-String } - LogGroup 'Pull request - Labels' { + Set-GitHubLogGroup 'Pull request - Labels' { $labels = @() $labels += $pull_request.labels.name $labels | Format-List | Out-String } - LogGroup 'Calculate release type' { + Set-GitHubLogGroup 'Calculate release type' { $createRelease = $isMerged -and $targetIsDefaultBranch $closedPullRequest = $prIsClosed -and -not $isMerged $createPrerelease = $labels -Contains 'prerelease' -and -not $createRelease -and -not $closedPullRequest @@ -135,7 +138,7 @@ Write-Output '-------------------------------------------------' } - LogGroup 'Get latest version - GitHub' { + Set-GitHubLogGroup 'Get latest version - GitHub' { $releases = gh release list --json 'createdAt,isDraft,isLatest,isPrerelease,name,publishedAt,tagName' | ConvertFrom-Json if ($LASTEXITCODE -ne 0) { Write-Error 'Failed to list all releases for the repo.' @@ -146,7 +149,7 @@ $latestRelease = $releases | Where-Object { $_.isLatest -eq $true } $latestRelease | Format-List | Out-String $ghReleaseVersionString = $latestRelease.tagName - if ($ghReleaseVersionString | IsNotNullOrEmpty) { + if (-not [string]::IsNullOrEmpty($ghReleaseVersionString)) { $ghReleaseVersion = New-PSSemVer -Version $ghReleaseVersionString } else { Write-Warning 'Could not find the latest release version. Using ''0.0.0'' as the version.' @@ -158,7 +161,7 @@ Write-Output '-------------------------------------------------' } - LogGroup 'Get latest version - PSGallery' { + Set-GitHubLogGroup 'Get latest version - PSGallery' { $count = 5 $delay = 10 for ($i = 1; $i -le $count; $i++) { @@ -187,7 +190,7 @@ Write-Output '-------------------------------------------------' } - LogGroup 'Get latest version - Manifest' { + Set-GitHubLogGroup 'Get latest version - Manifest' { Add-PSModulePath -Path (Split-Path -Path $ModulePath -Parent) $manifestFilePath = Join-Path $ModulePath "$Name.psd1" Write-Output "Module manifest file path: [$manifestFilePath]" @@ -198,7 +201,7 @@ try { $manifestVersion = New-PSSemVer -Version (Test-ModuleManifest $manifestFilePath -Verbose:$false).Version } catch { - if ($manifestVersion | IsNullOrEmpty) { + if ([string]::IsNullOrEmpty($manifestVersion)) { Write-Warning 'Could not find the module version in the manifest. Using ''0.0.0'' as the version.' $manifestVersion = New-PSSemVer -Version '0.0.0' } @@ -209,7 +212,7 @@ Write-Output '-------------------------------------------------' } - LogGroup 'Get latest version' { + Set-GitHubLogGroup 'Get latest version' { Write-Output "GitHub: [$($ghReleaseVersion.ToString())]" Write-Output "PSGallery: [$($psGalleryVersion.ToString())]" Write-Output "Manifest: [$($manifestVersion.ToString())] (ignored)" @@ -221,7 +224,7 @@ Write-Output '-------------------------------------------------' } - LogGroup 'Calculate new version' { + Set-GitHubLogGroup 'Calculate new version' { # - Increment based on label on PR $newVersion = New-PSSemVer -Version $latestVersion $newVersion.Prefix = $versionPrefix @@ -249,7 +252,7 @@ $newVersion.Prerelease = $prereleaseName Write-Output "Partial new version: [$newVersion]" - if ($datePrereleaseFormat | IsNotNullOrEmpty) { + if (-not [string]::IsNullOrEmpty($datePrereleaseFormat)) { Write-Output "Using date-based prerelease: [$datePrereleaseFormat]." $newVersion.Prerelease += "$(Get-Date -Format $datePrereleaseFormat)" Write-Output "Partial new version: [$newVersion]" @@ -303,7 +306,7 @@ } Write-Output "New version is [$($newVersion.ToString())]" - LogGroup 'Update module manifest' { + Set-GitHubLogGroup 'Update module manifest' { Write-Output 'Bump module version -> module metadata: Update-ModuleMetadata' $manifestNewVersion = "$($newVersion.Major).$($newVersion.Minor).$($newVersion.Patch)" Set-ModuleManifest -Path $manifestFilePath -ModuleVersion $manifestNewVersion -Verbose:$false @@ -315,12 +318,12 @@ Show-FileContent -Path $manifestFilePath } - LogGroup 'Install module dependencies' { + Set-GitHubLogGroup 'Install module dependencies' { Resolve-PSModuleDependency -ManifestFilePath $manifestFilePath } if ($createPrerelease -or $createRelease -or $whatIf) { - LogGroup 'Publish-ToPSGallery' { + Set-GitHubLogGroup 'Publish-ToPSGallery' { if ($createPrerelease) { $publishPSVersion = "$($newVersion.Major).$($newVersion.Minor).$($newVersion.Patch)-$($newVersion.Prerelease)" } else { @@ -344,7 +347,7 @@ " PowerShell Gallery [$publishPSVersion]($psGalleryReleaseLink) has been created.'" ) } else { - Write-GitHubNotice "Module [$Name - $publishPSVersion] published to the PowerShell Gallery." + Write-Host "::notice::Module [$Name - $publishPSVersion] published to the PowerShell Gallery." gh pr comment $pull_request.number -b "Module [$Name - $publishPSVersion]($psGalleryReleaseLink) published to the PowerShell Gallery." if ($LASTEXITCODE -ne 0) { Write-Error 'Failed to comment on the pull request.' @@ -353,7 +356,7 @@ } } - LogGroup 'New-GitHubRelease' { + Set-GitHubLogGroup 'New-GitHubRelease' { Write-Output 'Create new GitHub release' if ($createPrerelease) { if ($whatIf) { @@ -385,17 +388,17 @@ exit $LASTEXITCODE } } - Write-GitHubNotice "Release created: [$newVersion]" + Write-Host "::notice::Release created: [$newVersion]" } } - LogGroup 'List prereleases using the same name' { + Set-GitHubLogGroup 'List prereleases using the same name' { $prereleasesToCleanup = $releases | Where-Object { $_.tagName -like "*$prereleaseName*" } $prereleasesToCleanup | Select-Object -Property name, publishedAt, isPrerelease, isLatest | Format-Table | Out-String } if ((($closedPullRequest -or $createRelease) -and $autoCleanup) -or $whatIf) { - LogGroup "Cleanup prereleases for [$prereleaseName]" { + Set-GitHubLogGroup "Cleanup prereleases for [$prereleaseName]" { foreach ($rel in $prereleasesToCleanup) { $relTagName = $rel.tagName Write-Output "Deleting prerelease: [$relTagName]." diff --git a/scripts/main.ps1 b/scripts/main.ps1 index b946e9c..a1d014d 100644 --- a/scripts/main.ps1 +++ b/scripts/main.ps1 @@ -1,15 +1,33 @@ -[CmdletBinding()] +[CmdletBinding()] param() +$retryCount = 5 +$retryDelay = 10 +for ($i = 0; $i -lt $retryCount; $i++) { + try { + Install-PSResource -Name 'PSSemVer' -TrustRepository -Repository PSGallery + break + } catch { + Write-Warning "Installation of $($psResourceParams.Name) failed with error: $_" + if ($i -eq $retryCount - 1) { + throw + } + Write-Warning "Retrying in $retryDelay seconds..." + Start-Sleep -Seconds $retryDelay + } +} + $path = (Join-Path -Path $PSScriptRoot -ChildPath 'helpers') -LogGroup "Loading helper scripts from [$path]" { +Set-GitHubLogGroup "Loading helper scripts from [$path]" { Get-ChildItem -Path $path -Filter '*.ps1' -Recurse | ForEach-Object { Write-Verbose "[$($_.FullName)]" . $_.FullName } } -LogGroup 'Loading inputs' { +$env:GITHUB_REPOSITORY_NAME = $env:GITHUB_REPOSITORY -replace '.+/' + +Set-GitHubLogGroup 'Loading inputs' { $name = if ([string]::IsNullOrEmpty($env:PSMODULE_PUBLISH_PSMODULE_INPUT_Name)) { $env:GITHUB_REPOSITORY_NAME } else {