Skip to content

Commit adbfe66

Browse files
Refactor PowerForge Studio orchestration onto shared host services (#212)
* Add reusable GitHub housekeeping workflows * Simplify housekeeping composite action * Add housekeeping workflow summaries * Refactor GitHub housekeeping and project build * Add multi-runtime tool release builds * Refactor studio orchestration onto shared host services
1 parent 145dcfd commit adbfe66

152 files changed

Lines changed: 13530 additions & 3475 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
Lines changed: 159 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,159 @@
1+
[CmdletBinding()]
2+
param()
3+
4+
$ErrorActionPreference = 'Stop'
5+
6+
$repoRoot = (Resolve-Path (Join-Path $env:GITHUB_ACTION_PATH "../..")).Path
7+
$project = Join-Path $repoRoot "PowerForge.Cli/PowerForge.Cli.csproj"
8+
9+
function Format-GiB {
10+
param([long] $Bytes)
11+
12+
if ($Bytes -le 0) {
13+
return '0.0 GiB'
14+
}
15+
16+
return ('{0:N1} GiB' -f ($Bytes / 1GB))
17+
}
18+
19+
function Write-MarkdownSummary {
20+
param([string[]] $Lines)
21+
22+
if ([string]::IsNullOrWhiteSpace($env:GITHUB_STEP_SUMMARY)) {
23+
return
24+
}
25+
26+
Add-Content -Path $env:GITHUB_STEP_SUMMARY -Value ($Lines -join [Environment]::NewLine)
27+
}
28+
29+
function Resolve-ConfigPath {
30+
$configPath = $env:INPUT_CONFIG_PATH
31+
if ([string]::IsNullOrWhiteSpace($configPath)) {
32+
$configPath = '.powerforge/github-housekeeping.json'
33+
}
34+
35+
if ([System.IO.Path]::IsPathRooted($configPath)) {
36+
return [System.IO.Path]::GetFullPath($configPath)
37+
}
38+
39+
if ([string]::IsNullOrWhiteSpace($env:GITHUB_WORKSPACE)) {
40+
throw 'GITHUB_WORKSPACE is not set.'
41+
}
42+
43+
return [System.IO.Path]::GetFullPath((Join-Path $env:GITHUB_WORKSPACE $configPath))
44+
}
45+
46+
function Write-HousekeepingSummary {
47+
param([pscustomobject] $Envelope)
48+
49+
if (-not $Envelope.result) {
50+
return
51+
}
52+
53+
$result = $Envelope.result
54+
$lines = @(
55+
"### GitHub housekeeping",
56+
"",
57+
"- Mode: $(if ($result.dryRun) { 'dry-run' } else { 'apply' })",
58+
"- Requested sections: $((@($result.requestedSections) -join ', '))",
59+
"- Completed sections: $((@($result.completedSections) -join ', '))",
60+
"- Failed sections: $((@($result.failedSections) -join ', '))",
61+
"- Success: $(if ($Envelope.success) { 'yes' } else { 'no' })"
62+
)
63+
64+
if ($result.message) {
65+
$lines += "- Message: $($result.message)"
66+
}
67+
68+
if ($result.caches) {
69+
$lines += ''
70+
$lines += '#### Caches'
71+
if ($result.caches.usageBefore) {
72+
$lines += "- Usage before: $($result.caches.usageBefore.activeCachesCount) caches, $(Format-GiB ([long]$result.caches.usageBefore.activeCachesSizeInBytes))"
73+
}
74+
if ($result.caches.usageAfter) {
75+
$lines += "- Usage after: $($result.caches.usageAfter.activeCachesCount) caches, $(Format-GiB ([long]$result.caches.usageAfter.activeCachesSizeInBytes))"
76+
}
77+
$lines += "- Planned deletes: $($result.caches.plannedDeletes) ($(Format-GiB ([long]$result.caches.plannedDeleteBytes)))"
78+
$lines += "- Deleted: $($result.caches.deletedCaches) ($(Format-GiB ([long]$result.caches.deletedBytes)))"
79+
$lines += "- Failed deletes: $($result.caches.failedDeletes)"
80+
}
81+
82+
if ($result.artifacts) {
83+
$lines += ''
84+
$lines += '#### Artifacts'
85+
$lines += "- Planned deletes: $($result.artifacts.plannedDeletes) ($(Format-GiB ([long]$result.artifacts.plannedDeleteBytes)))"
86+
$lines += "- Deleted: $($result.artifacts.deletedArtifacts) ($(Format-GiB ([long]$result.artifacts.deletedBytes)))"
87+
$lines += "- Failed deletes: $($result.artifacts.failedDeletes)"
88+
}
89+
90+
if ($result.runner) {
91+
$lines += ''
92+
$lines += '#### Runner'
93+
$lines += "- Free before: $(Format-GiB ([long]$result.runner.freeBytesBefore))"
94+
$lines += "- Free after: $(Format-GiB ([long]$result.runner.freeBytesAfter))"
95+
$lines += "- Aggressive cleanup: $(if ($result.runner.aggressiveApplied) { 'yes' } else { 'no' })"
96+
}
97+
98+
Write-Host ("GitHub housekeeping: requested={0}; completed={1}; failed={2}" -f `
99+
(@($result.requestedSections) -join ','), `
100+
(@($result.completedSections) -join ','), `
101+
(@($result.failedSections) -join ','))
102+
103+
Write-MarkdownSummary -Lines ($lines + '')
104+
}
105+
106+
$configPath = Resolve-ConfigPath
107+
if (-not (Test-Path -LiteralPath $configPath)) {
108+
throw "Housekeeping config not found: $configPath"
109+
}
110+
111+
$arguments = [System.Collections.Generic.List[string]]::new()
112+
$arguments.AddRange(@(
113+
'run', '--project', $project, '-c', 'Release', '--no-build', '--',
114+
'github', 'housekeeping',
115+
'--config', $configPath
116+
))
117+
118+
if ($env:INPUT_APPLY -eq 'true') {
119+
$null = $arguments.Add('--apply')
120+
} else {
121+
$null = $arguments.Add('--dry-run')
122+
}
123+
124+
if (-not [string]::IsNullOrWhiteSpace($env:POWERFORGE_GITHUB_TOKEN)) {
125+
$null = $arguments.Add('--token')
126+
$null = $arguments.Add($env:POWERFORGE_GITHUB_TOKEN)
127+
}
128+
129+
$null = $arguments.Add('--output')
130+
$null = $arguments.Add('json')
131+
132+
$rawOutput = (& dotnet $arguments 2>&1 | Out-String).Trim()
133+
$exitCode = $LASTEXITCODE
134+
135+
if ([string]::IsNullOrWhiteSpace($rawOutput)) {
136+
if ($exitCode -ne 0) {
137+
throw "PowerForge housekeeping failed with exit code $exitCode and produced no output."
138+
}
139+
140+
return
141+
}
142+
143+
try {
144+
$envelope = $rawOutput | ConvertFrom-Json -Depth 30
145+
} catch {
146+
Write-Host $rawOutput
147+
throw
148+
}
149+
150+
Write-HousekeepingSummary -Envelope $envelope
151+
152+
if (-not $envelope.success) {
153+
Write-Host $rawOutput
154+
if ($envelope.exitCode) {
155+
exit [int]$envelope.exitCode
156+
}
157+
158+
exit 1
159+
}
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
# PowerForge GitHub Housekeeping
2+
3+
Reusable composite action that runs the config-driven `powerforge github housekeeping` command from `PowerForge.Cli`.
4+
5+
## What it does
6+
7+
- Loads housekeeping settings from a repo config file, typically `.powerforge/github-housekeeping.json`
8+
- Runs artifact cleanup, cache cleanup, and optional runner cleanup from one C# entrypoint
9+
- Writes a workflow summary with the requested sections plus before/after cleanup stats
10+
11+
## Recommended usage
12+
13+
Use the reusable workflow for the leanest repo wiring:
14+
15+
```yaml
16+
permissions:
17+
contents: read
18+
actions: write
19+
20+
jobs:
21+
housekeeping:
22+
uses: EvotecIT/PSPublishModule/.github/workflows/reusable-github-housekeeping.yml@main
23+
with:
24+
config-path: ./.powerforge/github-housekeeping.json
25+
secrets: inherit
26+
```
27+
28+
## Direct action usage
29+
30+
```yaml
31+
permissions:
32+
contents: read
33+
actions: write
34+
35+
jobs:
36+
housekeeping:
37+
runs-on: ubuntu-latest
38+
steps:
39+
- uses: actions/checkout@v4
40+
- uses: EvotecIT/PSPublishModule/.github/actions/github-housekeeping@main
41+
with:
42+
config-path: ./.powerforge/github-housekeeping.json
43+
github-token: ${{ secrets.GITHUB_TOKEN }}
44+
```
45+
46+
## Notes
47+
48+
- Cache and artifact deletion need `actions: write`.
49+
- Set `apply: "false"` to preview without deleting anything.
50+
- Hosted-runner repos should usually keep `runner.enabled` set to `false` in config.
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
name: PowerForge GitHub Housekeeping
2+
description: Run config-driven GitHub housekeeping using PowerForge.Cli.
3+
4+
inputs:
5+
config-path:
6+
description: Path to the housekeeping config file inside the checked-out repository.
7+
required: false
8+
default: ".powerforge/github-housekeeping.json"
9+
apply:
10+
description: When true, apply deletions. When false, run in dry-run mode.
11+
required: false
12+
default: "true"
13+
github-token:
14+
description: Optional token override for remote GitHub cleanup.
15+
required: false
16+
default: ""
17+
18+
runs:
19+
using: composite
20+
steps:
21+
- name: Setup .NET SDK
22+
uses: actions/setup-dotnet@v4
23+
with:
24+
global-json-file: ${{ github.action_path }}/../../global.json
25+
26+
- name: Build PowerForge CLI
27+
shell: pwsh
28+
run: |
29+
$repoRoot = (Resolve-Path (Join-Path $env:GITHUB_ACTION_PATH "../..")).Path
30+
$project = Join-Path $repoRoot "PowerForge.Cli/PowerForge.Cli.csproj"
31+
dotnet build $project -c Release
32+
env:
33+
DOTNET_NOLOGO: true
34+
DOTNET_CLI_TELEMETRY_OPTOUT: 1
35+
36+
- name: Run GitHub housekeeping
37+
shell: pwsh
38+
run: ${{ github.action_path }}/Invoke-PowerForgeHousekeeping.ps1
39+
env:
40+
DOTNET_NOLOGO: true
41+
DOTNET_CLI_TELEMETRY_OPTOUT: 1
42+
INPUT_CONFIG_PATH: ${{ inputs['config-path'] }}
43+
INPUT_APPLY: ${{ inputs.apply }}
44+
POWERFORGE_GITHUB_TOKEN: ${{ inputs['github-token'] != '' && inputs['github-token'] || github.token }}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
name: GitHub Housekeeping
2+
3+
on:
4+
schedule:
5+
- cron: '17 */6 * * *'
6+
workflow_dispatch:
7+
inputs:
8+
apply:
9+
description: 'Apply deletions (true/false)'
10+
required: false
11+
default: 'true'
12+
13+
permissions:
14+
actions: write
15+
contents: read
16+
17+
concurrency:
18+
group: github-housekeeping-${{ github.repository }}
19+
cancel-in-progress: false
20+
21+
jobs:
22+
housekeeping:
23+
uses: ./.github/workflows/reusable-github-housekeeping.yml
24+
with:
25+
config-path: ./.powerforge/github-housekeeping.json
26+
apply: ${{ github.event_name == 'workflow_dispatch' && inputs.apply == 'true' || github.event_name != 'workflow_dispatch' }}
27+
powerforge-ref: ${{ github.sha }}
28+
secrets:
29+
github-token: ${{ secrets.GITHUB_TOKEN }}
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
name: Reusable GitHub Housekeeping
2+
3+
on:
4+
workflow_call:
5+
inputs:
6+
config-path:
7+
description: Path to the housekeeping config file in the caller repository.
8+
required: false
9+
default: ".powerforge/github-housekeeping.json"
10+
type: string
11+
apply:
12+
description: Whether the run should apply deletions.
13+
required: false
14+
default: true
15+
type: boolean
16+
powerforge-ref:
17+
description: PSPublishModule ref used to resolve the shared housekeeping action.
18+
required: false
19+
default: "main"
20+
type: string
21+
secrets:
22+
github-token:
23+
required: false
24+
25+
permissions:
26+
actions: write
27+
contents: read
28+
29+
jobs:
30+
housekeeping:
31+
runs-on: ubuntu-latest
32+
timeout-minutes: 20
33+
steps:
34+
- uses: actions/checkout@v4
35+
36+
- name: Checkout PSPublishModule
37+
uses: actions/checkout@v4
38+
with:
39+
repository: EvotecIT/PSPublishModule
40+
ref: ${{ inputs['powerforge-ref'] }}
41+
path: .powerforge/pspublishmodule
42+
43+
- name: Run PowerForge housekeeping
44+
uses: ./.powerforge/pspublishmodule/.github/actions/github-housekeeping
45+
with:
46+
config-path: ${{ inputs['config-path'] }}
47+
apply: ${{ inputs.apply && 'true' || 'false' }}
48+
github-token: ${{ secrets['github-token'] != '' && secrets['github-token'] || github.token }}

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -201,6 +201,8 @@ DocProject/Help/html
201201

202202
# Click-Once directory
203203
publish/
204+
!PowerForgeStudio.Domain/Publish/
205+
!PowerForgeStudio.Domain/Publish/*.cs
204206

205207
# Publish Web Output
206208
*.[Pp]ublish.xml
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
{
2+
"$schema": "https://raw.githubusercontent.com/EvotecIT/PSPublishModule/main/Schemas/github.housekeeping.schema.json",
3+
"repository": "EvotecIT/PSPublishModule",
4+
"tokenEnvName": "GITHUB_TOKEN",
5+
"dryRun": false,
6+
"artifacts": {
7+
"enabled": true,
8+
"keepLatestPerName": 10,
9+
"maxAgeDays": 7,
10+
"maxDelete": 200
11+
},
12+
"caches": {
13+
"enabled": true,
14+
"keepLatestPerKey": 2,
15+
"maxAgeDays": 14,
16+
"maxDelete": 200
17+
},
18+
"runner": {
19+
"enabled": false
20+
}
21+
}

0 commit comments

Comments
 (0)