Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
b6d0b3e
feat: Gradient Module Scaffolding ( Fixes #1 )
StartAutomating Dec 29, 2025
2a04c34
feat: Gradient Action ( Fixes #7 )
StartAutomating Dec 29, 2025
66c63a3
feat: Gradient Build ( Fixes #2 )
StartAutomating Dec 29, 2025
de7f74b
feat: Gradient Module Scaffolding ( Fixes #1 )
StartAutomating Dec 29, 2025
c9511c2
feat: Gradient.GradientTypePattern ( Fixes #15 )
StartAutomating Dec 29, 2025
2b9f7a5
feat: Gradient.GradientTypePattern ( Fixes #15 )
Dec 29, 2025
0126c6a
feat: Gradient.GradientType ( Fixes #16 )
StartAutomating Dec 29, 2025
e60adc2
feat: Gradient.GradientType ( Fixes #16 )
Dec 29, 2025
ac6a3f5
feat: Gradient.CSS ( Fixes #4 )
StartAutomating Dec 29, 2025
a26e5c4
feat: Gradient.CSS ( Fixes #4 )
Dec 29, 2025
918f442
feat: Gradient.ToString ( Fixes #17 )
StartAutomating Dec 29, 2025
44370e3
feat: Gradient.ToString ( Fixes #17 )
Dec 29, 2025
bedb2c5
feat: Gradient.SVG ( Fixes #5 )
StartAutomating Dec 29, 2025
fd790c1
feat: Gradient.SVG ( Fixes #5 )
Dec 29, 2025
a2be0e5
feat: Gradient.DefaultDisplay ( Fixes #18 )
StartAutomating Dec 29, 2025
96b17ba
feat: Gradient.DefaultDisplay ( Fixes #18 )
Dec 29, 2025
a4de927
feat: Gradient Scaffolding ( Fixes #1 )
StartAutomating Dec 29, 2025
973c88e
feat: Get-Gradient ( Fixes #3 )
StartAutomating Dec 29, 2025
f91c63f
docs: code of conduct ( Fixes #9 )
StartAutomating Dec 29, 2025
1c670b5
docs: contributing ( Fixes #10 )
StartAutomating Dec 29, 2025
06e5a29
docs: security ( Fixes #11 )
StartAutomating Dec 29, 2025
71acee5
docs: Gradient Examples ( Fixes #13 )
StartAutomating Dec 29, 2025
b968ae9
feat: Gradient tests ( Fixes #8 )
StartAutomating Dec 29, 2025
7791d13
feat: Gradient sponsorship ( Fixes #14 )
StartAutomating Dec 29, 2025
649392b
release: Gradient 0.1 ( Fixes #1 )
StartAutomating Dec 29, 2025
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/BuildGradient.yml

Large diffs are not rendered by default.

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

* Import Gradient
* If `-Run` is provided, run that script
* Otherwise, unless `-SkipScriptFile` is passed, run all *.Gradient.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 Gradient.
# 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 *.Gradient.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 username@noreply.github.com.
[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 = 'Gradient'
$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 = "$UserName@noreply.github.com" }
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/BuildGradient.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/Gradient.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 "UseGradient" -Description 'Gradient Generator' -Action GradientAction -Icon chevron-right -OutputPath .\action.yml

Pop-Location
Loading
Loading