Add reusable GitHub housekeeping workflows#211
Conversation
PR Review: Add reusable GitHub housekeeping workflowsOverall this is a well-structured addition. The service architecture is clean, the CLI refactor removes real duplication, and the composite action design (build-from-source at the referenced commit) is a reasonable explicit choice. Below are findings grouped by severity. Bugs1. 2. 3. Pagination not exercised in tests Security4. Token passed via CLI argument 5. if (!target.StartsWith(expectedRoot, StringComparison.Ordinal))
throw new InvalidOperationException($"Refusing to delete outside runner root: {target}");before invoking Code Quality6. Duplicate 7. 8. 9. 10. TOCTOU race in Minor / Nits
The core logic is solid and the test infrastructure (fake HTTP handler, real temp filesystem tests) is a good foundation. Addressing the |
|
test |
PSPublishModule - PowerShell ModuleDescriptionThis module is a module builder helper that helps build PowerShell modules "Evotec way". Each module that is published to PowerShellGallery has Build or Publish folder next to it and within it there is PS1 script which executes module building that uses this module builder. For examples and usage you can visit other projects and see how they are built in addition to what is shown in Build folder. Support This ProjectIf you find this project helpful, please consider supporting its development. It takes a lot of time and effort to create and maintain this project. To become a sponsor, you can choose from the following options: Your sponsorship is completely optional and not required for using this project. If you work for a company that uses our .NET libraries or PowerShell Modules, Thank you for considering supporting this project! InstallingInstall-Module -Name PSPublishModule -AllowClobber -Force -SkipPublisherCheckForce and AllowClobber and SkipPublisherCheck aren't necessary but they do skip errors in case some appear. UpdatingUpdate-Module -Name PSPublishModuleThat's it. Whenever there's a new version you simply run the command and you can enjoy it. Remember, that you may need to close, reopen the PowerShell session if you have already used the module before updating it. The important thing is if something works for you on production, keep using it till you test the new version on a test computer. I do changes that may not be big, but big enough that auto-update will break your code. For example, small rename to a parameter and your code stops working! Be responsible! Documentation - Local, GitHub, Azure DevOpsPSPublishModule can bundle your module's Internals (Scripts, Docs, Binaries, Config) and root README/CHANGELOG/LICENSE. Use PSMaintenance to view docs with a rich UI (README + CHANGELOG + License + Docs tabs, dependencies graph, commands help).
Documentation Generation (Markdown, External Help, about_*)PowerForge can generate PowerShell help from a built module:
To enable it in your build config:
Authoring C# cmdlets (recommended):
/// <example>
/// <summary>Build a module</summary>
/// <code>Invoke-ModuleBuild -ModuleName 'MyModule' -Path 'C:\\Git\\MyModule'</code>
/// </example>Authoring
UsageCLI (PowerForge.Cli)This project includes a reusable C# core ( GitHub artifact quota cleanup (safe defaults): # dry-run by default (uses GITHUB_REPOSITORY + GITHUB_TOKEN)
powerforge github artifacts prune --name "test-results*,coverage*,github-pages"
# apply deletions
powerforge github artifacts prune --apply --keep 5 --max-age-days 7 --max-delete 200GitHub cache cleanup and runner housekeeping: # GitHub Actions cache cleanup (dry-run by default)
powerforge github caches prune --key "ubuntu-*,windows-*" --keep 1 --max-age-days 14
# Runner cleanup for hosted/self-hosted GitHub Actions runners
powerforge github runner cleanup --apply --min-free-gb 20If you want the shortest workflow possible across repos, use the reusable composite action: permissions:
contents: read
actions: write
jobs:
housekeeping:
runs-on: ubuntu-latest
steps:
- uses: EvotecIT/PSPublishModule/.github/actions/github-housekeeping@main
with:
github-token: ${{ secrets.GITHUB_TOKEN }}Introduced in 1.0.0 a new way to build PowerShell module based on DSL language. New-PrepareModule -ModuleName "YourModule" -Path "C:\DirectoryWhereTheModuleWillBeCreated"Once run the script will precrate the module folder with nessecary fies The structure of the module has all the basics that are required to start building the module. The module is ready to be built and published. The configuration example below shows how PSPublishModule is built internally. It's a good starting point for building your own module. Build-Module -ModuleName 'PSPublishModule' {
# Usual defaults as per standard module
$Manifest = [ordered] @{
ModuleVersion = '1.0.X'
CompatiblePSEditions = @('Desktop', 'Core')
GUID = 'eb76426a-1992-40a5-82cd-6480f883ef4d'
Author = 'Przemyslaw Klys'
CompanyName = 'Evotec'
Copyright = "(c) 2011 - $((Get-Date).Year) Przemyslaw Klys @ Evotec. All rights reserved."
Description = 'Simple project allowing preparing, managing, building and publishing modules to PowerShellGallery'
PowerShellVersion = '5.1'
Tags = @('Windows', 'MacOS', 'Linux', 'Build', 'Module')
IconUri = 'https://evotec.xyz/wp-content/uploads/2019/02/PSPublishModule.png'
ProjectUri = 'https://github.com/EvotecIT/PSPublishModule'
DotNetFrameworkVersion = '4.5.2'
}
New-ConfigurationManifest @Manifest
# Add standard module dependencies (directly, but can be used with loop as well)
New-ConfigurationModule -Type RequiredModule -Name 'powershellget' -Guid 'Auto' -Version 'Latest'
New-ConfigurationModule -Type RequiredModule -Name 'PSScriptAnalyzer' -Guid 'Auto' -Version 'Latest'
# Do not add inbox Microsoft.PowerShell.* modules as Required/External dependencies.
# Add approved modules, that can be used as a dependency, but only when specific function from those modules is used
# And on that time only that function and dependant functions will be copied over
# Keep in mind it has it's limits when "copying" functions such as it should not depend on DLLs or other external files
New-ConfigurationModule -Type ApprovedModule -Name 'PSSharedGoods', 'PSWriteColor', 'Connectimo', 'PSUnifi', 'PSWebToolbox', 'PSMyPassword'
#New-ConfigurationModuleSkip -IgnoreFunctionName 'Invoke-Formatter', 'Find-Module'
$ConfigurationFormat = [ordered] @{
RemoveComments = $false
PlaceOpenBraceEnable = $true
PlaceOpenBraceOnSameLine = $true
PlaceOpenBraceNewLineAfter = $true
PlaceOpenBraceIgnoreOneLineBlock = $false
PlaceCloseBraceEnable = $true
PlaceCloseBraceNewLineAfter = $true
PlaceCloseBraceIgnoreOneLineBlock = $false
PlaceCloseBraceNoEmptyLineBefore = $true
UseConsistentIndentationEnable = $true
UseConsistentIndentationKind = 'space'
UseConsistentIndentationPipelineIndentation = 'IncreaseIndentationAfterEveryPipeline'
UseConsistentIndentationIndentationSize = 4
UseConsistentWhitespaceEnable = $true
UseConsistentWhitespaceCheckInnerBrace = $true
UseConsistentWhitespaceCheckOpenBrace = $true
UseConsistentWhitespaceCheckOpenParen = $true
UseConsistentWhitespaceCheckOperator = $true
UseConsistentWhitespaceCheckPipe = $true
UseConsistentWhitespaceCheckSeparator = $true
AlignAssignmentStatementEnable = $true
AlignAssignmentStatementCheckHashtable = $true
UseCorrectCasingEnable = $true
}
# format PSD1 and PSM1 files when merging into a single file
# enable formatting is not required as Configuration is provided
New-ConfigurationFormat -ApplyTo 'OnMergePSM1', 'OnMergePSD1' -Sort None @ConfigurationFormat
# format PSD1 and PSM1 files within the module
# enable formatting is required to make sure that formatting is applied (with default settings)
New-ConfigurationFormat -ApplyTo 'DefaultPSD1', 'DefaultPSM1' -EnableFormatting -Sort None
# when creating PSD1 use special style without comments and with only required parameters
New-ConfigurationFormat -ApplyTo 'DefaultPSD1', 'OnMergePSD1' -PSD1Style 'Minimal'
# configuration for documentation, at the same time it enables documentation processing
New-ConfigurationDocumentation -Enable:$false -StartClean -UpdateWhenNew -PathReadme 'Docs\Readme.md' -Path 'Docs'
New-ConfigurationImportModule -ImportSelf -ImportRequiredModules
New-ConfigurationBuild -Enable:$true -SignModule -DeleteTargetModuleBeforeBuild -MergeModuleOnBuild -CertificateThumbprint '36A8A2D0E227D81A2D3B60DCE0CFCF23BEFC343B'
New-ConfigurationArtefact -Type Unpacked -Enable -Path "$PSScriptRoot\..\Artefacts" -RequiredModulesPath "$PSScriptRoot\..\Artefacts\Modules"
New-ConfigurationArtefact -Type Packed -Enable -Path "$PSScriptRoot\..\Releases" -IncludeTagName
# global options for publishing to github/psgallery
New-ConfigurationPublish -Type PowerShellGallery -FilePath 'C:\Support\Important\PowerShellGalleryAPI.txt' -Enabled:$false
New-ConfigurationPublish -Type GitHub -FilePath 'C:\Support\Important\GitHubAPI.txt' -UserName 'EvotecIT' -Enabled:$false
}The old way still works, but is less preferred. It's kept for backwards compatibility. $Configuration = @{
Information = @{
ModuleName = 'PSPublishModule'
#DirectoryProjects = 'C:\Support\GitHub'
# Where from to export aliases / functions
FunctionsToExport = 'Public'
AliasesToExport = 'Public'
# Those options below are not nessecary but can be used to configure other options. Those are "defaults"
Exclude = '.*', 'Ignore', 'Examples', 'package.json', 'Publish', 'Docs'
IncludeRoot = '*.psm1', '*.psd1', 'License*'
IncludePS1 = 'Private', 'Public', 'Enums', 'Classes'
IncludeAll = 'Images\', 'Resources\', 'Templates\', 'Bin\', 'Lib\', 'Data\'
IncludeCustomCode = {
}
IncludeToArray = @{
'Rules' = 'Examples'
}
LibrariesCore = 'Lib\Core'
LibrariesDefault = 'Lib\Default'
LibrariesStandard = 'Lib\Standard'
# manifest information
Manifest = @{
# Version number of this module.
ModuleVersion = '1.0.0'
# Supported PSEditions
CompatiblePSEditions = @('Desktop', 'Core')
# ID used to uniquely identify this module
GUID = 'eb76426a-1992-40a5-82cd-6480f883ef4d'
# Author of this module
Author = 'Przemyslaw Klys'
# Company or vendor of this module
CompanyName = 'Evotec'
# Copyright statement for this module
Copyright = "(c) 2011 - $((Get-Date).Year) Przemyslaw Klys @ Evotec. All rights reserved."
# Description of the functionality provided by this module
Description = 'Simple project allowing preparing, managing, building and publishing modules to PowerShellGallery'
# Minimum version of the Windows PowerShell engine required by this module
PowerShellVersion = '5.1'
# Functions to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no functions to export.
Tags = @('Windows', 'MacOS', 'Linux', 'Build', 'Module')
IconUri = 'https://evotec.xyz/wp-content/uploads/2019/02/PSPublishModule.png'
ProjectUri = 'https://github.com/EvotecIT/PSPublishModule'
RequiredModules = @(
@{ ModuleName = 'powershellget'; ModuleVersion = "2.2.5"; Guid = '1d73a601-4a6c-43c5-ba3f-619b18bbb404' }
@{ ModuleName = 'PSScriptAnalyzer'; ModuleVersion = "Latest"; Guid = 'd6245802-193d-4068-a631-8863a4342a18' }
)
ExternalModuleDependencies = @()
DotNetFrameworkVersion = '4.5.2'
}
}
Options = @{
Merge = @{
Sort = 'None'
FormatCodePSM1 = @{
Enabled = $false
RemoveComments = $false
FormatterSettings = @{
IncludeRules = @(
'PSPlaceOpenBrace',
'PSPlaceCloseBrace',
'PSUseConsistentWhitespace',
'PSUseConsistentIndentation',
'PSAlignAssignmentStatement',
'PSUseCorrectCasing'
)
Rules = @{
PSPlaceOpenBrace = @{
Enable = $true
OnSameLine = $true
NewLineAfter = $true
IgnoreOneLineBlock = $true
}
PSPlaceCloseBrace = @{
Enable = $true
NewLineAfter = $false
IgnoreOneLineBlock = $true
NoEmptyLineBefore = $false
}
PSUseConsistentIndentation = @{
Enable = $true
Kind = 'space'
PipelineIndentation = 'IncreaseIndentationAfterEveryPipeline'
IndentationSize = 4
}
PSUseConsistentWhitespace = @{
Enable = $true
CheckInnerBrace = $true
CheckOpenBrace = $true
CheckOpenParen = $true
CheckOperator = $true
CheckPipe = $true
CheckSeparator = $true
}
PSAlignAssignmentStatement = @{
Enable = $true
CheckHashtable = $true
}
PSUseCorrectCasing = @{
Enable = $true
}
}
}
}
FormatCodePSD1 = @{
Enabled = $true
RemoveComments = $false
}
Integrate = @{
ApprovedModules = 'PSSharedGoods', 'PSWriteColor', 'Connectimo', 'PSUnifi', 'PSWebToolbox', 'PSMyPassword'
}
# Style = @{
# PSD1 = 'Native'
# }
}
Standard = @{
FormatCodePSM1 = @{
}
FormatCodePSD1 = @{
Enabled = $true
#RemoveComments = $true
}
# Style = @{
# PSD1 = 'Native'
# }
}
PowerShellGallery = @{
ApiKey = 'C:\Support\Important\PowerShellGalleryAPI.txt'
FromFile = $true
}
GitHub = @{
ApiKey = 'C:\Support\Important\GithubAPI.txt'
FromFile = $true
UserName = 'EvotecIT'
#RepositoryName = 'PSPublishModule' # not required, uses project name
}
Documentation = @{
Path = 'Docs'
PathReadme = 'Docs\Readme.md'
}
Style = @{
PSD1 = 'Minimal' # Native
}
Signing = @{
CertificateThumbprint = '36A8A2D0E227D81A2D3B60DCE0CFCF23BEFC343B'
}
}
Steps = @{
BuildLibraries = @{
Enable = $false # build once every time nuget gets updated
Configuration = 'Release'
Framework = 'netstandard2.0', 'net472'
#ProjectName = 'ImagePlayground.PowerShell'
}
BuildModule = @{ # requires Enable to be on to process all of that
Enable = $true
DeleteBefore = $false
Merge = $true
MergeMissing = $true
SignMerged = $true
CreateFileCatalog = $false
Releases = $true
#ReleasesUnpacked = $false
ReleasesUnpacked = @{
Enabled = $true
IncludeTagName = $false
Path = "$PSScriptRoot\..\Artefacts"
RequiredModules = @{
Enabled = $true
Path = "$PSScriptRoot\..\Artefacts\Modules"
}
DirectoryOutput = @{
}
FilesOutput = @{
}
}
RefreshPSD1Only = $false
# only when there are classes
ClassesDotSource = $false
LibrarySeparateFile = $false
LibraryDotSource = $false
# Applicable only for non-merge/publish situation
# It's simply to make life easier during debugging
# It makes all functions/aliases exportable
UseWildcardForFunctions = $false
# special features for binary modules
DebugDLL = $false
ResolveBinaryConflicts = $false # mostly for memory and other libraries
# ResolveBinaryConflicts = @{
# ProjectName = 'ImagePlayground.PowerShell'
# }
LocalVersion = $false # bumps version in PSD1 on every build
}
BuildDocumentation = @{
Enable = $true # enables documentation processing
StartClean = $true # always starts clean
UpdateWhenNew = $true # always updates right after update
}
ImportModules = @{
Self = $true
RequiredModules = $false
Verbose = $false
}
PublishModule = @{ # requires Enable to be on to process all of that
Enabled = $false
Prerelease = ''
RequireForce = $false
GitHub = $false
}
}
}
Build-Module -Configuration $Configuration |
1 similar comment
PSPublishModule - PowerShell ModuleDescriptionThis module is a module builder helper that helps build PowerShell modules "Evotec way". Each module that is published to PowerShellGallery has Build or Publish folder next to it and within it there is PS1 script which executes module building that uses this module builder. For examples and usage you can visit other projects and see how they are built in addition to what is shown in Build folder. Support This ProjectIf you find this project helpful, please consider supporting its development. It takes a lot of time and effort to create and maintain this project. To become a sponsor, you can choose from the following options: Your sponsorship is completely optional and not required for using this project. If you work for a company that uses our .NET libraries or PowerShell Modules, Thank you for considering supporting this project! InstallingInstall-Module -Name PSPublishModule -AllowClobber -Force -SkipPublisherCheckForce and AllowClobber and SkipPublisherCheck aren't necessary but they do skip errors in case some appear. UpdatingUpdate-Module -Name PSPublishModuleThat's it. Whenever there's a new version you simply run the command and you can enjoy it. Remember, that you may need to close, reopen the PowerShell session if you have already used the module before updating it. The important thing is if something works for you on production, keep using it till you test the new version on a test computer. I do changes that may not be big, but big enough that auto-update will break your code. For example, small rename to a parameter and your code stops working! Be responsible! Documentation - Local, GitHub, Azure DevOpsPSPublishModule can bundle your module's Internals (Scripts, Docs, Binaries, Config) and root README/CHANGELOG/LICENSE. Use PSMaintenance to view docs with a rich UI (README + CHANGELOG + License + Docs tabs, dependencies graph, commands help).
Documentation Generation (Markdown, External Help, about_*)PowerForge can generate PowerShell help from a built module:
To enable it in your build config:
Authoring C# cmdlets (recommended):
/// <example>
/// <summary>Build a module</summary>
/// <code>Invoke-ModuleBuild -ModuleName 'MyModule' -Path 'C:\\Git\\MyModule'</code>
/// </example>Authoring
UsageCLI (PowerForge.Cli)This project includes a reusable C# core ( GitHub artifact quota cleanup (safe defaults): # dry-run by default (uses GITHUB_REPOSITORY + GITHUB_TOKEN)
powerforge github artifacts prune --name "test-results*,coverage*,github-pages"
# apply deletions
powerforge github artifacts prune --apply --keep 5 --max-age-days 7 --max-delete 200GitHub cache cleanup and runner housekeeping: # GitHub Actions cache cleanup (dry-run by default)
powerforge github caches prune --key "ubuntu-*,windows-*" --keep 1 --max-age-days 14
# Runner cleanup for hosted/self-hosted GitHub Actions runners
powerforge github runner cleanup --apply --min-free-gb 20If you want the shortest workflow possible across repos, use the reusable composite action: permissions:
contents: read
actions: write
jobs:
housekeeping:
runs-on: ubuntu-latest
steps:
- uses: EvotecIT/PSPublishModule/.github/actions/github-housekeeping@main
with:
github-token: ${{ secrets.GITHUB_TOKEN }}Introduced in 1.0.0 a new way to build PowerShell module based on DSL language. New-PrepareModule -ModuleName "YourModule" -Path "C:\DirectoryWhereTheModuleWillBeCreated"Once run the script will precrate the module folder with nessecary fies The structure of the module has all the basics that are required to start building the module. The module is ready to be built and published. The configuration example below shows how PSPublishModule is built internally. It's a good starting point for building your own module. Build-Module -ModuleName 'PSPublishModule' {
# Usual defaults as per standard module
$Manifest = [ordered] @{
ModuleVersion = '1.0.X'
CompatiblePSEditions = @('Desktop', 'Core')
GUID = 'eb76426a-1992-40a5-82cd-6480f883ef4d'
Author = 'Przemyslaw Klys'
CompanyName = 'Evotec'
Copyright = "(c) 2011 - $((Get-Date).Year) Przemyslaw Klys @ Evotec. All rights reserved."
Description = 'Simple project allowing preparing, managing, building and publishing modules to PowerShellGallery'
PowerShellVersion = '5.1'
Tags = @('Windows', 'MacOS', 'Linux', 'Build', 'Module')
IconUri = 'https://evotec.xyz/wp-content/uploads/2019/02/PSPublishModule.png'
ProjectUri = 'https://github.com/EvotecIT/PSPublishModule'
DotNetFrameworkVersion = '4.5.2'
}
New-ConfigurationManifest @Manifest
# Add standard module dependencies (directly, but can be used with loop as well)
New-ConfigurationModule -Type RequiredModule -Name 'powershellget' -Guid 'Auto' -Version 'Latest'
New-ConfigurationModule -Type RequiredModule -Name 'PSScriptAnalyzer' -Guid 'Auto' -Version 'Latest'
# Do not add inbox Microsoft.PowerShell.* modules as Required/External dependencies.
# Add approved modules, that can be used as a dependency, but only when specific function from those modules is used
# And on that time only that function and dependant functions will be copied over
# Keep in mind it has it's limits when "copying" functions such as it should not depend on DLLs or other external files
New-ConfigurationModule -Type ApprovedModule -Name 'PSSharedGoods', 'PSWriteColor', 'Connectimo', 'PSUnifi', 'PSWebToolbox', 'PSMyPassword'
#New-ConfigurationModuleSkip -IgnoreFunctionName 'Invoke-Formatter', 'Find-Module'
$ConfigurationFormat = [ordered] @{
RemoveComments = $false
PlaceOpenBraceEnable = $true
PlaceOpenBraceOnSameLine = $true
PlaceOpenBraceNewLineAfter = $true
PlaceOpenBraceIgnoreOneLineBlock = $false
PlaceCloseBraceEnable = $true
PlaceCloseBraceNewLineAfter = $true
PlaceCloseBraceIgnoreOneLineBlock = $false
PlaceCloseBraceNoEmptyLineBefore = $true
UseConsistentIndentationEnable = $true
UseConsistentIndentationKind = 'space'
UseConsistentIndentationPipelineIndentation = 'IncreaseIndentationAfterEveryPipeline'
UseConsistentIndentationIndentationSize = 4
UseConsistentWhitespaceEnable = $true
UseConsistentWhitespaceCheckInnerBrace = $true
UseConsistentWhitespaceCheckOpenBrace = $true
UseConsistentWhitespaceCheckOpenParen = $true
UseConsistentWhitespaceCheckOperator = $true
UseConsistentWhitespaceCheckPipe = $true
UseConsistentWhitespaceCheckSeparator = $true
AlignAssignmentStatementEnable = $true
AlignAssignmentStatementCheckHashtable = $true
UseCorrectCasingEnable = $true
}
# format PSD1 and PSM1 files when merging into a single file
# enable formatting is not required as Configuration is provided
New-ConfigurationFormat -ApplyTo 'OnMergePSM1', 'OnMergePSD1' -Sort None @ConfigurationFormat
# format PSD1 and PSM1 files within the module
# enable formatting is required to make sure that formatting is applied (with default settings)
New-ConfigurationFormat -ApplyTo 'DefaultPSD1', 'DefaultPSM1' -EnableFormatting -Sort None
# when creating PSD1 use special style without comments and with only required parameters
New-ConfigurationFormat -ApplyTo 'DefaultPSD1', 'OnMergePSD1' -PSD1Style 'Minimal'
# configuration for documentation, at the same time it enables documentation processing
New-ConfigurationDocumentation -Enable:$false -StartClean -UpdateWhenNew -PathReadme 'Docs\Readme.md' -Path 'Docs'
New-ConfigurationImportModule -ImportSelf -ImportRequiredModules
New-ConfigurationBuild -Enable:$true -SignModule -DeleteTargetModuleBeforeBuild -MergeModuleOnBuild -CertificateThumbprint '36A8A2D0E227D81A2D3B60DCE0CFCF23BEFC343B'
New-ConfigurationArtefact -Type Unpacked -Enable -Path "$PSScriptRoot\..\Artefacts" -RequiredModulesPath "$PSScriptRoot\..\Artefacts\Modules"
New-ConfigurationArtefact -Type Packed -Enable -Path "$PSScriptRoot\..\Releases" -IncludeTagName
# global options for publishing to github/psgallery
New-ConfigurationPublish -Type PowerShellGallery -FilePath 'C:\Support\Important\PowerShellGalleryAPI.txt' -Enabled:$false
New-ConfigurationPublish -Type GitHub -FilePath 'C:\Support\Important\GitHubAPI.txt' -UserName 'EvotecIT' -Enabled:$false
}The old way still works, but is less preferred. It's kept for backwards compatibility. $Configuration = @{
Information = @{
ModuleName = 'PSPublishModule'
#DirectoryProjects = 'C:\Support\GitHub'
# Where from to export aliases / functions
FunctionsToExport = 'Public'
AliasesToExport = 'Public'
# Those options below are not nessecary but can be used to configure other options. Those are "defaults"
Exclude = '.*', 'Ignore', 'Examples', 'package.json', 'Publish', 'Docs'
IncludeRoot = '*.psm1', '*.psd1', 'License*'
IncludePS1 = 'Private', 'Public', 'Enums', 'Classes'
IncludeAll = 'Images\', 'Resources\', 'Templates\', 'Bin\', 'Lib\', 'Data\'
IncludeCustomCode = {
}
IncludeToArray = @{
'Rules' = 'Examples'
}
LibrariesCore = 'Lib\Core'
LibrariesDefault = 'Lib\Default'
LibrariesStandard = 'Lib\Standard'
# manifest information
Manifest = @{
# Version number of this module.
ModuleVersion = '1.0.0'
# Supported PSEditions
CompatiblePSEditions = @('Desktop', 'Core')
# ID used to uniquely identify this module
GUID = 'eb76426a-1992-40a5-82cd-6480f883ef4d'
# Author of this module
Author = 'Przemyslaw Klys'
# Company or vendor of this module
CompanyName = 'Evotec'
# Copyright statement for this module
Copyright = "(c) 2011 - $((Get-Date).Year) Przemyslaw Klys @ Evotec. All rights reserved."
# Description of the functionality provided by this module
Description = 'Simple project allowing preparing, managing, building and publishing modules to PowerShellGallery'
# Minimum version of the Windows PowerShell engine required by this module
PowerShellVersion = '5.1'
# Functions to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no functions to export.
Tags = @('Windows', 'MacOS', 'Linux', 'Build', 'Module')
IconUri = 'https://evotec.xyz/wp-content/uploads/2019/02/PSPublishModule.png'
ProjectUri = 'https://github.com/EvotecIT/PSPublishModule'
RequiredModules = @(
@{ ModuleName = 'powershellget'; ModuleVersion = "2.2.5"; Guid = '1d73a601-4a6c-43c5-ba3f-619b18bbb404' }
@{ ModuleName = 'PSScriptAnalyzer'; ModuleVersion = "Latest"; Guid = 'd6245802-193d-4068-a631-8863a4342a18' }
)
ExternalModuleDependencies = @()
DotNetFrameworkVersion = '4.5.2'
}
}
Options = @{
Merge = @{
Sort = 'None'
FormatCodePSM1 = @{
Enabled = $false
RemoveComments = $false
FormatterSettings = @{
IncludeRules = @(
'PSPlaceOpenBrace',
'PSPlaceCloseBrace',
'PSUseConsistentWhitespace',
'PSUseConsistentIndentation',
'PSAlignAssignmentStatement',
'PSUseCorrectCasing'
)
Rules = @{
PSPlaceOpenBrace = @{
Enable = $true
OnSameLine = $true
NewLineAfter = $true
IgnoreOneLineBlock = $true
}
PSPlaceCloseBrace = @{
Enable = $true
NewLineAfter = $false
IgnoreOneLineBlock = $true
NoEmptyLineBefore = $false
}
PSUseConsistentIndentation = @{
Enable = $true
Kind = 'space'
PipelineIndentation = 'IncreaseIndentationAfterEveryPipeline'
IndentationSize = 4
}
PSUseConsistentWhitespace = @{
Enable = $true
CheckInnerBrace = $true
CheckOpenBrace = $true
CheckOpenParen = $true
CheckOperator = $true
CheckPipe = $true
CheckSeparator = $true
}
PSAlignAssignmentStatement = @{
Enable = $true
CheckHashtable = $true
}
PSUseCorrectCasing = @{
Enable = $true
}
}
}
}
FormatCodePSD1 = @{
Enabled = $true
RemoveComments = $false
}
Integrate = @{
ApprovedModules = 'PSSharedGoods', 'PSWriteColor', 'Connectimo', 'PSUnifi', 'PSWebToolbox', 'PSMyPassword'
}
# Style = @{
# PSD1 = 'Native'
# }
}
Standard = @{
FormatCodePSM1 = @{
}
FormatCodePSD1 = @{
Enabled = $true
#RemoveComments = $true
}
# Style = @{
# PSD1 = 'Native'
# }
}
PowerShellGallery = @{
ApiKey = 'C:\Support\Important\PowerShellGalleryAPI.txt'
FromFile = $true
}
GitHub = @{
ApiKey = 'C:\Support\Important\GithubAPI.txt'
FromFile = $true
UserName = 'EvotecIT'
#RepositoryName = 'PSPublishModule' # not required, uses project name
}
Documentation = @{
Path = 'Docs'
PathReadme = 'Docs\Readme.md'
}
Style = @{
PSD1 = 'Minimal' # Native
}
Signing = @{
CertificateThumbprint = '36A8A2D0E227D81A2D3B60DCE0CFCF23BEFC343B'
}
}
Steps = @{
BuildLibraries = @{
Enable = $false # build once every time nuget gets updated
Configuration = 'Release'
Framework = 'netstandard2.0', 'net472'
#ProjectName = 'ImagePlayground.PowerShell'
}
BuildModule = @{ # requires Enable to be on to process all of that
Enable = $true
DeleteBefore = $false
Merge = $true
MergeMissing = $true
SignMerged = $true
CreateFileCatalog = $false
Releases = $true
#ReleasesUnpacked = $false
ReleasesUnpacked = @{
Enabled = $true
IncludeTagName = $false
Path = "$PSScriptRoot\..\Artefacts"
RequiredModules = @{
Enabled = $true
Path = "$PSScriptRoot\..\Artefacts\Modules"
}
DirectoryOutput = @{
}
FilesOutput = @{
}
}
RefreshPSD1Only = $false
# only when there are classes
ClassesDotSource = $false
LibrarySeparateFile = $false
LibraryDotSource = $false
# Applicable only for non-merge/publish situation
# It's simply to make life easier during debugging
# It makes all functions/aliases exportable
UseWildcardForFunctions = $false
# special features for binary modules
DebugDLL = $false
ResolveBinaryConflicts = $false # mostly for memory and other libraries
# ResolveBinaryConflicts = @{
# ProjectName = 'ImagePlayground.PowerShell'
# }
LocalVersion = $false # bumps version in PSD1 on every build
}
BuildDocumentation = @{
Enable = $true # enables documentation processing
StartClean = $true # always starts clean
UpdateWhenNew = $true # always updates right after update
}
ImportModules = @{
Self = $true
RequiredModules = $false
Verbose = $false
}
PublishModule = @{ # requires Enable to be on to process all of that
Enabled = $false
Prerelease = ''
RequireForce = $false
GitHub = $false
}
}
}
Build-Module -Configuration $Configuration |
BacklogPowerForge.Web (Engine)API Docs parity (DocFX-style)
Engine quality
PSPublishModule / PowerForge (DotNet Publish Engine)
Websites |
|
hello world |
PSPublishModule Changelog2.0.19 - 2025.06.17What's Changed
Full Changelog: v2.0.18...v2.0.19 2.0.18 - 2025.06.06What's new
2.0.17 - 2025.05.28
Get-ProjectVersion -Path "C:\Support\GitHub\DnsClientX" -ExcludeFolders @('C:\Support\GitHub\DnsClientX\Module\Artefacts')
Set-ProjectVersion -Path "C:\Support\GitHub\DnsClientX" -NewVersion "0.4.0"
Set-ProjectVersion -Path "C:\Support\GitHub\DnsClientX" -VersionType Build2.0.16 - 2024.10.27
2.0.15 - 2024.10.11What's Changed
New Contributors
Full Changelog: v2.0.14...v2.0.15 2.0.14 - 2024.08.14What's Changed
Full Changelog: v2.0.13...v2.0.14 2.0.13 - 2024.07.13
2.0.12 - 2024.05.06
2.0.11
2.0.10
2.0.9
2.0.8
2.0.7
2.0.6
2.0.5What's new
Full Changelog: v2.0.4...v2.0.5 2.0.4What's Changed
New Contributors
Full Changelog: v2.0.3...v2.0.4 2.0.3
2.0.2 - 2023.08.06
2.0.1 - 2023.08.04
2.0.0 - 2023.08.03
1.8.0-Preview5
1.8.0-Preview4
1.8.0-Preview3
1.8.0-Preview2
1.8.0-Preview1
1.7.0
1.6.0 - 2023.07.11
1.5.1 - 2023.07.06
1.5.0 - 2023.07.04
1.4.0 - 2023.07.03
1.3.0 - 2023.07.03
1.2.0 - 2023.06.23
1.1.0 - 2023.06.19
1.0.0 - 2023.06.18
0.9.53 - 2022.12.31
0.9.52 - 2022.12.30
I've removed the logic where Standard would always get loaded even if Default/Core was also provided which could lead to unexpected behavior.
0.9.51 - 2022.09.26
0.9.43 - 2022.04.14
|
Test: allow-sudo enables sudo rm |
Test: cleanup-runner: false in workflow |
|
forwarded via the token flag on command line. Use token-env instead. |
|
DryRun = true default on both specs is a safe default. |
|
PR Review summary - see below for full review posted in separate comment. |
|
test |
1 similar comment
|
MERGE BBF70FCADAD5A2FC28FD3749685D35BAF81D3E4E INTO 145DCFD7A2C789A71746C37B2EC653365BE9DE19 |
|
I'm very curious about your workflow here, @PrzemyslawKlys! :) Is Claude Code Review going back and creating release notes for all of your projects? |
Code Review TestTest body. |
Code Review: Add reusable GitHub housekeeping workflowsOverall this is a well-structured PR. The layering is clean (models → shared service → CLI command → composite action → reusable workflow), dry-run defaults are safe, the fake-HTTP test harness is solid, and rate-limit detection is a nice touch. Below are the issues I found, ordered by severity. Bug
When only caches are enabled and the repository is missing, The root fix is to gate both additions on their respective if (spec.Artifacts.Enabled) failed.Add("artifacts");
if (spec.Caches.Enabled) failed.Add("caches");None of the existing tests cover this path (caches-only + missing repo), which is why it slipped through. Security / Correctness
The
Passing the token via Design
When Workflow notes
The operator precedence in the
Calling repos that use the default Test coverage gapsScenarios not currently covered:
Positive notes
Summary: the artifact-in-failed-sections bug should be fixed before merge. The regex timeout and token-in-args concerns are worth addressing soon for production hardening. The rest are quality improvements that can be tracked separately. |
Code ReviewOverall this is a well-structured PR. The refactoring in Security
"PublishApiKeyFilePath": "C:\\Support\\Important\\NugetOrgEvotec.txt",
"GitHubAccessTokenFilePath": "C:\\Support\\Important\\GithubAPI.txt",
"CertificateThumbprint": "483292C9E317AA13B07BB7A96AE9D1A5ED9E7703",The file paths are developer-specific and won't work in CI. The certificate thumbprint is also now public. Consider either removing these keys from the committed config and relying on CI environment variables/secrets, or adding a GitHub Actions not pinned to commit SHAs
# instead of
uses: actions/checkout@v4
# prefer
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
Callers who omit this parameter will always track the tip of Bug / Behavioral Change
The original code had: if ($UseStaging -and -not $PSBoundParameters.ContainsKey('ClearOut')) {
$ClearOut = $true
}This block was removed. Now Code Quality
function Publish-GitHubAssets {
...
$tagName = if (...$selectedTools.Count -eq 1) { # $selectedTools is a script-level varPowerShell's dynamic scoping makes this work, but it's fragile — if the function is ever extracted or called from a different scope the reference will silently break. Pass
$rawOutput = (& dotnet $arguments 2>&1 | Out-String).Trim()
...
$envelope = $rawOutput | ConvertFrom-Json -Depth 30If dotnet writes anything to stderr (e.g. a first-run message, a .NET SDK warning, a missing global.json notice), $rawOutput = (& dotnet $arguments 2>$null | Out-String).Trim()
# or capture to a temp variable and log it only on failureMinor / Nice-to-have
apply: ${{ github.event_name == 'workflow_dispatch' && inputs.apply == 'true' || github.event_name != 'workflow_dispatch' }}Logic is correct but non-obvious. A brief inline comment would help: # scheduled runs always apply; manual dispatch respects the input
apply: ${{ github.event_name == 'workflow_dispatch' && inputs.apply == 'true' || github.event_name != 'workflow_dispatch' }}
Zip naming — switching from a timestamp suffix to Positives
|
Code Review: Add reusable GitHub housekeeping workflowsOverall this is a well-structured PR that adds a useful, config-driven housekeeping workflow. The service layer has good separation of concerns, dry-run support is well integrated throughout, and the test files cover the main happy paths. Below are my findings, organised by priority. 🔴 Issues to address1. Token exposed in process argument list (
|
Code ReviewOverall this is a well-structured PR that meaningfully consolidates package and tool release workflows into a unified entrypoint. The security fix for the sudo delete path traversal is especially appreciated. A few issues worth addressing before merge: 🔴 Issues1. Potential deadlock in The synchronous implementation reads stdout to completion before reading stderr: var stdOut = process.StandardOutput.ReadToEnd();
var stdErr = process.StandardError.ReadToEnd();
process.WaitForExit();If 2. $args = @('run', '--project', $project, ...)
dotnet @args
🟡 Potential Issues3. Hardcoded developer-local paths in committed "PublishApiKeyFilePath": "C:\\Support\\Important\\NugetOrgEvotec.txt",
"GitHubAccessTokenFilePath": "C:\\Support\\Important\\GithubAPI.txt",
"TokenFilePath": "C:\\Support\\Important\\GithubAPI.txt"These paths are specific to one developer's machine and will silently fail for anyone else. If the intent is "each developer fills in their own path," consider adding a comment in the JSON or a companion 4. Race condition in var now = DateTime.Now;
var utcNow = DateTime.UtcNow;These two calls happen on separate lines. At a second boundary, 5. This assigns 6. Config discovery includes overly-broad In 🟢 Suggestions (non-blocking)7. Simplify redundant ternary expressions ( // Current
PublishNuget = argv.Any(a => ...) ? true : null,
PublishNuget = argv.Any(a => ...) ? (bool?)true : null,or extract a local 8. Silent exception swallowing in try { File.Delete(file); } catch { }Best-effort cleanup is reasonable, but consider at least logging at verbose level when a file cannot be deleted. This would help diagnose cases where symbol files remain unexpectedly. 9. Reflection-based test for private method is fragile
✅ Positives
|
PR Review: Add reusable GitHub housekeeping workflows / unified release entrypointOverall this is a well-structured PR that consolidates a lot of duplicated build logic into a unified service layer. The architecture is clean and testable. A few issues worth addressing before merge: 🔴 Security / Breaking Issues1. The file includes hardcoded developer machine paths: "PublishApiKeyFilePath": "C:\\Support\\Important\\NugetOrgEvotec.txt",
"GitHubAccessTokenFilePath": "C:\\Support\\Important\\GithubAPI.txt",
"TokenFilePath": "C:\\Support\\Important\\GithubAPI.txt"These will fail for any other contributor or CI/CD runner, and 2. if (!fullTarget.StartsWith(fullRoot, StringComparison.OrdinalIgnoreCase))On Linux/macOS, the filesystem is case-sensitive. var rel = Path.GetRelativePath(root, fullTarget);
if (rel.StartsWith("..", StringComparison.Ordinal))
throw ...;🟡 Code Quality3. The old scripts both had: Set-StrictMode -Version Latest
$ErrorActionPreference = 'Stop'These were removed in the new versions. Without them, PowerShell errors may silently continue. Both new script bodies are short wrappers, but it's still a regression in robustness. 4. psi.Arguments = string.Join(" ", args);This requires manual quoting (the 5. var json = JsonSerializer.Serialize(manifest, new JsonSerializerOptions { WriteIndented = true });The rest of the codebase uses the AOT-safe 6. process.WaitForExit();If if (!process.WaitForExit(TimeSpan.FromMinutes(20)))
{
process.Kill(entireProcessTree: true);
throw new TimeoutException($"dotnet publish timed out for ...");
}Note: 7. var now = DateTime.Now; // local
var utcNow = DateTime.UtcNow; // UTC
8. catch { /* ignore */ }This swallows things like 🟢 Positive Observations
Minor / Nit
|


Summary
powerforge github caches pruneandpowerforge github runner cleanupVerification
dotnet build .\PowerForge\PowerForge.csproj -c Releasedotnet build .\PowerForge.Cli\PowerForge.Cli.csproj -c Releasedotnet test .\PowerForge.Tests\PowerForge.Tests.csproj -c Release --filter "FullyQualifiedName~GitHubActionsCacheCleanupServiceTests|FullyQualifiedName~RunnerHousekeepingServiceTests"Notes