Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
a489b9b
feat: Shape Module Scaffolding ( Fixes #1 )
StartAutomating Jan 4, 2026
831c52c
feat: Shape Action ( Fixes #3 )
StartAutomating Jan 4, 2026
d612fb6
feat: Shape build ( Fixes #2 )
StartAutomating Jan 4, 2026
83895fe
feat: Get-Shape ( Fixes #4 )
StartAutomating Jan 4, 2026
1dbab7f
feat: Shape.CSS ( Fixes #5 )
StartAutomating Jan 4, 2026
d4c9791
feat: Shape.CSS ( Fixes #5 )
Jan 4, 2026
06c0680
feat: Shape.ShapeTypePattern ( Fixes #8 )
StartAutomating Jan 4, 2026
fceaa65
feat: Shape.ShapeTypePattern ( Fixes #8 )
Jan 4, 2026
c4f685e
feat: Shape.Help ( Fixes #7 )
StartAutomating Jan 4, 2026
a4a5be1
feat: Shape.Help ( Fixes #7 )
Jan 4, 2026
865a779
feat: Shape.ShapeType ( Fixes #9 )
StartAutomating Jan 4, 2026
c70cdb1
feat: Shape.ShapeType ( Fixes #9 )
Jan 4, 2026
dc16f7b
feat: Shape.DefaultDisplay ( Fixes #10 )
StartAutomating Jan 4, 2026
6a9991c
feat: Shape.DefaultDisplay ( Fixes #10 )
Jan 4, 2026
aa798cb
feat: Shape.ToString ( Fixes #6 )
StartAutomating Jan 4, 2026
77a1f53
feat: Shape.ToString ( Fixes #6 )
Jan 4, 2026
3426b82
feat: Shape module Scaffolding ( Fixes #1 )
StartAutomating Jan 4, 2026
d512ce5
docs: Shape Code of Conduct ( Fixes #12 )
StartAutomating Jan 4, 2026
5f0b1c5
docs: Shape Contributing ( Fixes #13 )
StartAutomating Jan 4, 2026
3f5f9b8
docs: Shape Security ( Fixes #14 )
StartAutomating Jan 4, 2026
6fb2e85
docs: Shape Sponsorship ( Fixes #15 )
StartAutomating Jan 4, 2026
71de793
feat: Shape.CSS ( Fixes #5 )
StartAutomating Jan 4, 2026
f450bb6
feat: Shape.CSS ( Fixes #5 )
Jan 4, 2026
f396e80
feat: Shape.CSS ( Fixes #5 )
StartAutomating Jan 4, 2026
d2b56fd
feat: Shape.CSS ( Fixes #5 )
Jan 4, 2026
601e609
feat: Shape.Help ( Fixes #7 )
StartAutomating Jan 4, 2026
eb1c2e4
feat: Shape.Help ( Fixes #7 )
Jan 4, 2026
2d97a70
feat: Get-Shape aliases ( Fixes #4, Fixes #16 )
StartAutomating Jan 4, 2026
69af523
feat: Shape.ShapeType ( Fixes #9 )
StartAutomating Jan 4, 2026
6e43502
feat: Shape.ShapeType ( Fixes #9 )
Jan 4, 2026
0d9f5c6
feat: Get-Shape aliases ( Fixes #4, Fixes #16 )
StartAutomating Jan 4, 2026
f554d47
feat: Get-Shape aliases ( Fixes #4, Fixes #16 )
StartAutomating Jan 4, 2026
543e4c5
feat: Shape tests ( Fixes #11 )
StartAutomating Jan 4, 2026
bf6690a
feat: Shape README and example fixes ( Fixes #17 )
StartAutomating Jan 4, 2026
d375094
release: Shape 0.1
StartAutomating Jan 5, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/FUNDING.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
github: [StartAutomating]
502 changes: 502 additions & 0 deletions .github/workflows/Build.yml

Large diffs are not rendered by default.

351 changes: 351 additions & 0 deletions Build/GitHub/Actions/ShapeAction.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,351 @@
<#
.Synopsis
GitHub Action for Shape
.Description
GitHub Action for Shape. This will:

* Import Shape
* If `-Run` is provided, run that script
* Otherwise, unless `-SkipScriptFile` is passed, run all *.Shape.ps1 files beneath the workflow directory
* If any `-ActionScript` was provided, run scripts from the action path that match a wildcard pattern.

If you will be making changes using the GitHubAPI, you should provide a -GitHubToken

If none is provided, and ENV:GITHUB_TOKEN is set, this will be used instead.

Any files changed can be outputted by the script.
Those changes can be checked back into the repo if `-AutoCommit` is set.
#>

param(
# A PowerShell Script that uses Shape.
# Any files outputted from the script will be added to the repository.
# If those files have a .Message attached to them, they will be committed with that message.
[string]
$Run,

# If set, will not process any files named *.Shape.ps1
[switch]
$SkipScriptFile,

# A list of modules to be installed from the PowerShell gallery before scripts run.
[string[]]
$InstallModule,

# If provided, will commit any remaining changes made to the workspace with this commit message.
[string]
$CommitMessage,

# If provided, will checkout a new branch before making the changes.
# If not provided, will use the current branch.
[string]
$TargetBranch,

# The name of one or more scripts to run, from this action's path.
[string[]]
$ActionScript,

# The github token to use for requests.
[string]
$GitHubToken = '{{ secrets.GITHUB_TOKEN }}',

# The user email associated with a git commit. If this is not provided, it will be set to the [email protected].
[string]
$UserEmail,

# The user name associated with a git commit.
[string]
$UserName,

# If set, will not push any changes made to the repository.
# (they will still be committed if `-AutoCommit` is passed)
[switch]
$NoPush,

# If set, will commit any changes made to the repository.
# (this also implies `-NoPush`)
[switch]
$AutoCommit
)

$ErrorActionPreference = 'continue'
"::group::Parameters" | Out-Host
[PSCustomObject]$PSBoundParameters | Format-List | Out-Host
"::endgroup::" | Out-Host

$gitHubEventJson = [IO.File]::ReadAllText($env:GITHUB_EVENT_PATH)
$gitHubEvent =
if ($env:GITHUB_EVENT_PATH) {
$gitHubEventJson | ConvertFrom-Json
} else { $null }
"::group::Parameters" | Out-Host
$gitHubEvent | Format-List | Out-Host
"::endgroup::" | Out-Host


$anyFilesChanged = $false
$ActionModuleName = 'Shape'
$actorInfo = $null


$checkDetached = git symbolic-ref -q HEAD
if ($LASTEXITCODE) {
"::warning::On detached head, skipping action" | Out-Host
exit 0
}

function InstallActionModule {
param([string]$ModuleToInstall)
$moduleInWorkspace = Get-ChildItem -Path $env:GITHUB_WORKSPACE -Recurse -File |
Where-Object Name -eq "$($moduleToInstall).psd1" |
Where-Object {
$(Get-Content $_.FullName -Raw) -match 'ModuleVersion'
}
if (-not $moduleInWorkspace) {
$availableModules = Get-Module -ListAvailable
if ($availableModules.Name -notcontains $moduleToInstall) {
Install-Module $moduleToInstall -Scope CurrentUser -Force -AcceptLicense -AllowClobber
}
Import-Module $moduleToInstall -Force -PassThru | Out-Host
} else {
Import-Module $moduleInWorkspace.FullName -Force -PassThru | Out-Host
}
}
function ImportActionModule {
#region -InstallModule
if ($InstallModule) {
"::group::Installing Modules" | Out-Host
foreach ($moduleToInstall in $InstallModule) {
InstallActionModule -ModuleToInstall $moduleToInstall
}
"::endgroup::" | Out-Host
}
#endregion -InstallModule

if ($env:GITHUB_ACTION_PATH) {
$LocalModulePath = Join-Path $env:GITHUB_ACTION_PATH "$ActionModuleName.psd1"
if (Test-path $LocalModulePath) {
Import-Module $LocalModulePath -Force -PassThru | Out-String
} else {
throw "Module '$ActionModuleName' not found"
}
} elseif (-not (Get-Module $ActionModuleName)) {
throw "Module '$ActionModuleName' not found"
}

"::notice title=ModuleLoaded::$ActionModuleName Loaded from Path - $($LocalModulePath)" | Out-Host
if ($env:GITHUB_STEP_SUMMARY) {
"# $($ActionModuleName)" |
Out-File -Append -FilePath $env:GITHUB_STEP_SUMMARY
}
}
function InitializeAction {
#region Custom
#endregion Custom

# Configure git based on the $env:GITHUB_ACTOR
if (-not $UserName) { $UserName = $env:GITHUB_ACTOR }
if (-not $actorID) { $actorID = $env:GITHUB_ACTOR_ID }

$actorInfo =
Invoke-RestMethod -Uri "https://api.github.com/user/$actorID"


if (-not $UserEmail) { $UserEmail = "[email protected]" }
git config --global user.email $UserEmail
git config --global user.name $actorInfo.name

# Pull down any changes
git pull | Out-Host

if ($TargetBranch) {
"::notice title=Expanding target branch string $targetBranch" | Out-Host
$TargetBranch = $ExecutionContext.SessionState.InvokeCommand.ExpandString($TargetBranch)
"::notice title=Checking out target branch::$targetBranch" | Out-Host
git checkout -b $TargetBranch | Out-Host
git pull | Out-Host
}
}

function InvokeActionModule {
$myScriptStart = [DateTime]::Now
$myScript = $ExecutionContext.SessionState.PSVariable.Get("Run").Value
if ($myScript) {
Invoke-Expression -Command $myScript |
. ProcessOutput |
Out-Host
return
}
$myScriptTook = [Datetime]::Now - $myScriptStart
$MyScriptFilesStart = [DateTime]::Now

$myScriptList = @()
$shouldSkip = $ExecutionContext.SessionState.PSVariable.Get("SkipScriptFile").Value
if ($shouldSkip) {
return
}
$scriptFiles = @(
Get-ChildItem -Recurse -Path $env:GITHUB_WORKSPACE |
Where-Object Name -Match "\.$($ActionModuleName)\.ps1$"
if ($ActionScript) {
if ($ActionScript -match '^\s{0,}/' -and $ActionScript -match '/\s{0,}$') {
$ActionScriptPattern = $ActionScript.Trim('/').Trim() -as [regex]
if ($ActionScriptPattern) {
$ActionScriptPattern = [regex]::new($ActionScript.Trim('/').Trim(), 'IgnoreCase,IgnorePatternWhitespace', [timespan]::FromSeconds(0.5))
Get-ChildItem -Recurse -Path $env:GITHUB_ACTION_PATH |
Where-Object { $_.Name -Match "\.$($ActionModuleName)\.ps1$" -and $_.FullName -match $ActionScriptPattern }
}
} else {
Get-ChildItem -Recurse -Path $env:GITHUB_ACTION_PATH |
Where-Object Name -Match "\.$($ActionModuleName)\.ps1$" |
Where-Object FullName -Like $ActionScript
}
}
) | Select-Object -Unique
$scriptFiles |
ForEach-Object -Begin {
if ($env:GITHUB_STEP_SUMMARY) {
"## $ActionModuleName Scripts" |
Out-File -Append -FilePath $env:GITHUB_STEP_SUMMARY
}
} -Process {
$myScriptList += $_.FullName.Replace($env:GITHUB_WORKSPACE, '').TrimStart('/')
$myScriptCount++
$scriptFile = $_
if ($env:GITHUB_STEP_SUMMARY) {
"### $($scriptFile.Fullname -replace [Regex]::Escape($env:GITHUB_WORKSPACE))" |
Out-File -Append -FilePath $env:GITHUB_STEP_SUMMARY
}
$scriptCmd = $ExecutionContext.SessionState.InvokeCommand.GetCommand($scriptFile.FullName, 'ExternalScript')
foreach ($requiredModule in $CommandInfo.ScriptBlock.Ast.ScriptRequirements.RequiredModules) {
if ($requiredModule.Name -and
(-not $requiredModule.MaximumVersion) -and
(-not $requiredModule.RequiredVersion)
) {
InstallActionModule $requiredModule.Name
}
}
Push-Location $scriptFile.Directory.Fullname
$scriptFileOutputs = . $scriptCmd
$scriptFileOutputs |
. ProcessOutput |
Out-Host
Pop-Location
}

$MyScriptFilesTook = [Datetime]::Now - $MyScriptFilesStart
$SummaryOfMyScripts = "$myScriptCount $ActionModuleName scripts took $($MyScriptFilesTook.TotalSeconds) seconds"
$SummaryOfMyScripts |
Out-Host
if ($env:GITHUB_STEP_SUMMARY) {
$SummaryOfMyScripts |
Out-File -Append -FilePath $env:GITHUB_STEP_SUMMARY
}
#region Custom
#endregion Custom
}

function OutError {
$anyRuntimeExceptions = $false
foreach ($err in $error) {
$errParts = @(
"::error "
@(
if ($err.InvocationInfo.ScriptName) {
"file=$($err.InvocationInfo.ScriptName)"
}
if ($err.InvocationInfo.ScriptLineNumber -ge 1) {
"line=$($err.InvocationInfo.ScriptLineNumber)"
if ($err.InvocationInfo.OffsetInLine -ge 1) {
"col=$($err.InvocationInfo.OffsetInLine)"
}
}
if ($err.CategoryInfo.Activity) {
"title=$($err.CategoryInfo.Activity)"
}
) -join ','
"::"
$err.Exception.Message
if ($err.CategoryInfo.Category -eq 'OperationStopped' -and
$err.CategoryInfo.Reason -eq 'RuntimeException') {
$anyRuntimeExceptions = $true
}
) -join ''
$errParts | Out-Host
if ($anyRuntimeExceptions) {
exit 1
}
}
}

function PushActionOutput {
if ($anyFilesChanged) {
"::notice::$($anyFilesChanged) Files Changed" | Out-Host
}
if ($CommitMessage -or $anyFilesChanged) {
if ($CommitMessage) {
Get-ChildItem $env:GITHUB_WORKSPACE -Recurse |
ForEach-Object {
$gitStatusOutput = git status $_.Fullname -s
if ($gitStatusOutput) {
git add $_.Fullname
}
}

git commit -m $ExecutionContext.SessionState.InvokeCommand.ExpandString($CommitMessage)
}

$checkDetached = git symbolic-ref -q HEAD
if (-not $LASTEXITCODE -and -not $NoPush -and $AutoCommit) {
if ($TargetBranch -and $anyFilesChanged) {
"::notice::Pushing Changes to $targetBranch" | Out-Host
git push --set-upstream origin $TargetBranch
} elseif ($anyFilesChanged) {
"::notice::Pushing Changes" | Out-Host
git push
}
"Git Push Output: $($gitPushed | Out-String)"
} else {
"::notice::Not pushing changes (on detached head)" | Out-Host
$LASTEXITCODE = 0
exit 0
}
}
}

filter ProcessOutput {
$out = $_
$outItem = Get-Item -Path $out -ErrorAction Ignore
if (-not $outItem -and $out -is [string]) {
$out | Out-Host
if ($env:GITHUB_STEP_SUMMARY) {
"> $out" | Out-File -Append -FilePath $env:GITHUB_STEP_SUMMARY
}
return
}
$fullName, $shouldCommit =
if ($out -is [IO.FileInfo]) {
$out.FullName, (git status $out.Fullname -s)
} elseif ($outItem) {
$outItem.FullName, (git status $outItem.Fullname -s)
}
if ($shouldCommit -and $AutoCommit) {
"$fullName has changed, and should be committed" | Out-Host
git add $fullName
if ($out.Message) {
git commit -m "$($out.Message)" | Out-Host
} elseif ($out.CommitMessage) {
git commit -m "$($out.CommitMessage)" | Out-Host
} elseif ($gitHubEvent.head_commit.message) {
git commit -m "$($gitHubEvent.head_commit.message)" | Out-Host
}
$anyFilesChanged = $true
}
$out
}

. ImportActionModule
. InitializeAction
. InvokeActionModule
. PushActionOutput
. OutError
16 changes: 16 additions & 0 deletions Build/GitHub/Jobs/BuildShape.psd1
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
@{
"runs-on" = "ubuntu-latest"
if = '${{ success() }}'
steps = @(
@{
name = 'Check out repository'
uses = 'actions/checkout@main'
}
'RunEZOut'
@{
name = 'Run Action (on branch)'
if = '${{github.ref_name != ''main''}}'
uses = './'
}
)
}
10 changes: 10 additions & 0 deletions Build/GitHub/Steps/PublishTestResults.psd1
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
@{
name = 'PublishTestResults'
uses = 'actions/upload-artifact@main'
with = @{
name = 'PesterResults'
path = '**.TestResults.xml'
}
if = '${{always()}}'
}

10 changes: 10 additions & 0 deletions Build/Shape.GitHubAction.PSDevOps.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
#requires -Module PSDevOps
Import-BuildStep -SourcePath (
Join-Path $PSScriptRoot 'GitHub'
) -BuildSystem GitHubAction

$PSScriptRoot | Split-Path | Push-Location

New-GitHubAction -Name "NewShape" -Description 'Shape Generator' -Action ShapeAction -Icon chevron-right -OutputPath .\action.yml

Pop-Location
Loading
Loading