Skip to content

Commit 68a2a7f

Browse files
🌟 [Major]: Reusable workflows now use GitHub App orchestration and explicit publish credentials (#408)
Process-PSModule now performs repository inspection, versioning, release management, and pull-request feedback through a configured GitHub App. Publishing uses the explicit `PSGALLERY_API_KEY` credential consistently from the reusable workflow through the publishing action. ## Breaking Changes Caller workflows must now pass GitHub App credentials and the PowerShell Gallery credential through the reusable workflow contract. Workflows that omit these required secrets fail before their dependent stages run. ```yaml secrets: PSGALLERY_API_KEY: ${{ secrets.PSGALLERY_API_KEY }} GitHubAppClientId: ${{ secrets.GITHUB_APP_CLIENT_ID }} GitHubAppPrivateKey: ${{ secrets.GITHUB_APP_PRIVATE_KEY }} ``` The caller can retain any local GitHub App secret names; only the reusable-workflow boundary names are fixed. `PSGALLERY_API_KEY` is also the input name of the publishing action. ## Changed: Scoped GitHub automation Every GitHub-dependent stage now mints a short-lived token for the triggering repository and requests only the access it needs. Version planning reads repository and pull-request data, builds read repository metadata, and publishing creates releases, uploads assets, cleans prereleases, and posts pull-request comments through the configured App. The GitHub App installation needs Contents: write and Pull requests: write. Metadata: read is granted automatically. Permissions such as Actions, Statuses, Pages, and ID tokens remain part of the caller workflow's default `github.token` path and are not App permissions. ## Technical Details - Plan, Build-Module, and Publish-Module mint repository-scoped installation tokens with pinned `actions/create-github-app-token`. - GitHub-facing actions receive the token only through step-scoped `GH_TOKEN`; the GitHub App path has no `github.token` fallback. - The release path, version resolution, repository metadata reads, settings, comments, and prerelease cleanup all use the App token. - The reusable workflow, publishing action input, action environment variable, and Process-PSModule documentation use `PSGALLERY_API_KEY`. - Canonical caller templates and Process-PSModule documentation include the App credential contract, PowerShell Gallery credential, permission matrix, and Dependabot configuration requirement. - The GitHub-Script named-token-input hardening follow-up remains tracked separately. <details> <summary>Related issues</summary> - Fixes #510 - #343 - PSModule/GitHub-Script#103 </details> --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent 15c2814 commit 68a2a7f

20 files changed

Lines changed: 238 additions & 40 deletions

File tree

‎.github/actions/Cleanup-PSModulePrereleases/action.yml‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ runs:
3131
shell: pwsh
3232
working-directory: ${{ inputs.WorkingDirectory }}
3333
env:
34+
GH_TOKEN: ${{ env.GH_TOKEN }}
3435
PSMODULE_CLEANUP_PSMODULEPRERELEASES_INPUT_WhatIf: ${{ inputs.WhatIf }}
3536
PSMODULE_CLEANUP_PSMODULEPRERELEASES_CONTEXT_ReleaseTag: ${{ inputs.ReleaseTag }}
3637
run: ${{ github.action_path }}/src/cleanup.ps1

‎.github/actions/Get-PSModuleSettings/action.yml‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ runs:
5353
uses: PSModule/GitHub-Script@8083ec1f733f00357ee4d0db0c6056686e483bc0 # v1.9.0
5454
id: Get-PSModuleSettings
5555
env:
56+
GH_TOKEN: ${{ env.GH_TOKEN }}
5657
PSMODULE_GET_SETTINGS_INPUT_Name: ${{ inputs.Name }}
5758
PSMODULE_GET_SETTINGS_INPUT_SettingsPath: ${{ inputs.SettingsPath }}
5859
PSMODULE_GET_SETTINGS_INPUT_Debug: ${{ inputs.Debug }}
@@ -63,6 +64,7 @@ runs:
6364
PSMODULE_GET_SETTINGS_INPUT_ImportantFilePatterns: ${{ inputs.ImportantFilePatterns }}
6465
with:
6566
Name: Get-PSModuleSettings
67+
Token: ${{ env.GH_TOKEN }}
6668
ShowInfo: false
6769
ShowOutput: true
6870
Debug: ${{ inputs.Debug }}

‎.github/actions/Publish-PSModule/README.md‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ Publishes a pre-versioned PowerShell module artifact to the PowerShell Gallery.
99
| `Name` | Name of the module to publish. | No | Repository name |
1010
| `ModulePath` | Path containing the built `<Name>/` module directory. | No | `outputs/module` |
1111
| `ArtifactName` | Name of the module artifact to download. | No | `module` |
12-
| `APIKey` | PowerShell Gallery API key. | Yes | N/A |
12+
| `PSGALLERY_API_KEY` | PowerShell Gallery API key. | Yes | N/A |
1313
| `WhatIf` | Logs publishing operations without publishing the module. | No | `false` |
1414
| `WorkingDirectory` | Directory where the publishing script runs. | No | `.` |
1515

@@ -26,7 +26,7 @@ This action does not provide outputs.
2626
Name: ExampleModule
2727
ModulePath: outputs/module
2828
ArtifactName: module
29-
APIKey: ${{ secrets.APIKEY }}
29+
PSGALLERY_API_KEY: ${{ secrets.PSGALLERY_API_KEY }}
3030
```
3131
3232
Use [Release-PSModule](../Release-PSModule/README.md) in a separate workflow step to create the GitHub release from the same artifact.

‎.github/actions/Publish-PSModule/action.yml‎

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ inputs:
1010
description: Path to the folder containing the <Name>/ module subdirectory from Build-PSModule.
1111
required: false
1212
default: outputs/module
13-
APIKey:
13+
PSGALLERY_API_KEY:
1414
description: PowerShell Gallery API Key.
1515
required: true
1616
WhatIf:
@@ -46,8 +46,9 @@ runs:
4646
shell: pwsh
4747
working-directory: ${{ inputs.WorkingDirectory }}
4848
env:
49+
GH_TOKEN: ${{ env.GH_TOKEN }}
4950
PSMODULE_PUBLISH_PSMODULE_INPUT_Name: ${{ inputs.Name }}
5051
PSMODULE_PUBLISH_PSMODULE_INPUT_ModulePath: ${{ inputs.ModulePath }}
51-
PSMODULE_PUBLISH_PSMODULE_INPUT_APIKey: ${{ inputs.APIKey }}
52+
PSMODULE_PUBLISH_PSMODULE_INPUT_PSGALLERY_API_KEY: ${{ inputs.PSGALLERY_API_KEY }}
5253
PSMODULE_PUBLISH_PSMODULE_INPUT_WhatIf: ${{ inputs.WhatIf }}
5354
run: ${{ github.action_path }}/src/publish.ps1

‎.github/actions/Publish-PSModule/src/publish.ps1‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
[Diagnostics.CodeAnalysis.SuppressMessageAttribute(
2-
'PSUseDeclaredVarsMoreThanAssignments', 'apiKey',
2+
'PSUseDeclaredVarsMoreThanAssignments', 'psGalleryApiKey',
33
Justification = 'Variable is used in script blocks.'
44
)]
55
[Diagnostics.CodeAnalysis.SuppressMessageAttribute(
@@ -38,7 +38,7 @@ LogGroup 'Load inputs' {
3838
exit 1
3939
}
4040
$modulePath = Resolve-Path -Path $modulePathCandidate | Select-Object -ExpandProperty Path
41-
$apiKey = $env:PSMODULE_PUBLISH_PSMODULE_INPUT_APIKey
41+
$psGalleryApiKey = $env:PSMODULE_PUBLISH_PSMODULE_INPUT_PSGALLERY_API_KEY
4242
$whatIf = $env:PSMODULE_PUBLISH_PSMODULE_INPUT_WhatIf -eq 'true'
4343

4444
Write-Host "Module name: [$name]"
@@ -138,7 +138,7 @@ LogGroup 'Publish to PSGallery' {
138138
Write-Host "Publish-PSResource -Path $modulePath -Repository PSGallery -ApiKey ***"
139139
} else {
140140
try {
141-
Publish-PSResource -Path $modulePath -Repository PSGallery -ApiKey $apiKey
141+
Publish-PSResource -Path $modulePath -Repository PSGallery -ApiKey $psGalleryApiKey
142142
} catch {
143143
Write-Error $_.Exception.Message
144144
exit 1

‎.github/actions/Resolve-PSModuleVersion/action.yml‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ runs:
6565
shell: pwsh
6666
working-directory: ${{ inputs.WorkingDirectory }}
6767
env:
68-
GH_TOKEN: ${{ github.token }}
68+
GH_TOKEN: ${{ env.GH_TOKEN }}
6969
PSMODULE_RESOLVE_PSMODULEVERSION_INPUT_Settings: ${{ inputs.Settings }}
7070
PSMODULE_RESOLVE_PSMODULEVERSION_INPUT_Name: ${{ inputs.Name }}
7171
PSMODULE_RESOLVE_PSMODULEVERSION_INPUT_EventJson: ${{ inputs.EventJson }}

‎.github/workflows/Build-Module.yml‎

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,13 @@ name: Build-Module
22

33
on:
44
workflow_call:
5+
secrets:
6+
GitHubAppClientId:
7+
description: The client ID of the GitHub App used for repository API calls.
8+
required: true
9+
GitHubAppPrivateKey:
10+
description: The private key of the GitHub App used for repository API calls.
11+
required: true
512
inputs:
613
Settings:
714
type: string
@@ -20,8 +27,6 @@ jobs:
2027
Build-Module:
2128
name: Build-Module
2229
runs-on: ubuntu-latest
23-
env:
24-
GH_TOKEN: ${{ github.token }}
2530
steps:
2631
- name: Checkout Code
2732
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
@@ -36,8 +41,19 @@ jobs:
3641
path: _wf
3742
persist-credentials: false
3843

44+
- name: Create GitHub App token
45+
id: App-Token
46+
uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3.2.0
47+
with:
48+
client-id: ${{ secrets.GitHubAppClientId }}
49+
private-key: ${{ secrets.GitHubAppPrivateKey }}
50+
repositories: ${{ github.event.repository.name }}
51+
permission-metadata: read
52+
3953
- name: Build module
4054
uses: ./_wf/.github/actions/Build-PSModule
55+
env:
56+
GH_TOKEN: ${{ steps.App-Token.outputs.token }}
4157
with:
4258
Name: ${{ fromJson(inputs.Settings).Name }}
4359
Version: ${{ fromJson(inputs.Settings).Publish.Module.Resolution.Version != '' && fromJson(inputs.Settings).Publish.Module.Resolution.Version || '999.0.0' }}

‎.github/workflows/Plan.yml‎

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,13 @@ name: Plan
99

1010
on:
1111
workflow_call:
12+
secrets:
13+
GitHubAppClientId:
14+
description: The client ID of the GitHub App used for repository API calls.
15+
required: true
16+
GitHubAppPrivateKey:
17+
description: The private key of the GitHub App used for repository API calls.
18+
required: true
1219
inputs:
1320
SettingsPath:
1421
type: string
@@ -79,9 +86,21 @@ jobs:
7986
path: _wf
8087
persist-credentials: false
8188

89+
- name: Create GitHub App token
90+
id: App-Token
91+
uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3.2.0
92+
with:
93+
client-id: ${{ secrets.GitHubAppClientId }}
94+
private-key: ${{ secrets.GitHubAppPrivateKey }}
95+
repositories: ${{ github.event.repository.name }}
96+
permission-contents: read
97+
permission-pull-requests: write
98+
8299
- name: Get-Settings
83100
uses: ./_wf/.github/actions/Get-PSModuleSettings
84101
id: Get-Settings
102+
env:
103+
GH_TOKEN: ${{ steps.App-Token.outputs.token }}
85104
with:
86105
SettingsPath: ${{ inputs.SettingsPath }}
87106
Debug: ${{ inputs.Debug }}
@@ -95,7 +114,7 @@ jobs:
95114
uses: ./_wf/.github/actions/Resolve-PSModuleVersion
96115
id: Resolve-Version
97116
env:
98-
GH_TOKEN: ${{ github.token }}
117+
GH_TOKEN: ${{ steps.App-Token.outputs.token }}
99118
with:
100119
Settings: ${{ steps.Get-Settings.outputs.Settings }}
101120
Name: ${{ fromJson(steps.Get-Settings.outputs.Settings).Name }}

‎.github/workflows/Publish-Module.yml‎

Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,15 @@ name: Publish-Module
33
on:
44
workflow_call:
55
secrets:
6-
APIKey:
6+
PSGALLERY_API_KEY:
77
description: The API key for the PowerShell Gallery.
88
required: true
9+
GitHubAppClientId:
10+
description: The client ID of the GitHub App used for repository API calls.
11+
required: true
12+
GitHubAppPrivateKey:
13+
description: The private key of the GitHub App used for repository API calls.
14+
required: true
915
inputs:
1016
Settings:
1117
type: string
@@ -37,15 +43,25 @@ jobs:
3743
path: _wf
3844
persist-credentials: false
3945

46+
- name: Create GitHub App token
47+
id: App-Token
48+
uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3.2.0
49+
with:
50+
client-id: ${{ secrets.GitHubAppClientId }}
51+
private-key: ${{ secrets.GitHubAppPrivateKey }}
52+
repositories: ${{ github.event.repository.name }}
53+
permission-contents: write
54+
permission-pull-requests: write
55+
4056
- name: Publish module
4157
if: fromJson(inputs.Settings).Publish.Module.Resolution.ReleaseType != 'None'
4258
uses: ./_wf/.github/actions/Publish-PSModule
4359
env:
44-
GH_TOKEN: ${{ github.token }}
60+
GH_TOKEN: ${{ steps.App-Token.outputs.token }}
4561
with:
4662
Name: ${{ fromJson(inputs.Settings).Name }}
4763
ModulePath: outputs/module
48-
APIKey: ${{ secrets.APIKey }}
64+
PSGALLERY_API_KEY: ${{ secrets.PSGALLERY_API_KEY }}
4965
WhatIf: ${{ github.repository == 'PSModule/Process-PSModule' }}
5066
WorkingDirectory: ${{ fromJson(inputs.Settings).WorkingDirectory }}
5167

@@ -54,7 +70,7 @@ jobs:
5470
if: always() && !cancelled() && fromJson(inputs.Settings).Publish.Module.Resolution.ReleaseType != 'None'
5571
uses: ./_wf/.github/actions/Release-PSModule
5672
env:
57-
GH_TOKEN: ${{ github.token }}
73+
GH_TOKEN: ${{ steps.App-Token.outputs.token }}
5874
with:
5975
Name: ${{ fromJson(inputs.Settings).Name }}
6076
ModulePath: outputs/module
@@ -74,7 +90,7 @@ jobs:
7490
steps.create-github-release.outcome == 'success')
7591
uses: ./_wf/.github/actions/Cleanup-PSModulePrereleases
7692
env:
77-
GH_TOKEN: ${{ github.token }}
93+
GH_TOKEN: ${{ steps.App-Token.outputs.token }}
7894
with:
7995
WhatIf: ${{ github.repository == 'PSModule/Process-PSModule' }}
8096
AutoCleanup: ${{ fromJson(inputs.Settings).Publish.Module.AutoCleanup }}

‎.github/workflows/Workflow-Test-Default.yml‎

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,9 @@ jobs:
3030
if: ${{ github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository }}
3131
uses: ./.github/workflows/workflow.yml
3232
secrets:
33-
APIKey: ${{ secrets.APIKey }}
33+
PSGALLERY_API_KEY: ${{ secrets.PSGALLERY_API_KEY }}
34+
GitHubAppClientId: ${{ secrets.SHELLY_CLIENT_ID }}
35+
GitHubAppPrivateKey: ${{ secrets.SHELLY_PRIVATE_KEY }}
3436
TestData: >-
3537
{
3638
"secrets": {

0 commit comments

Comments
 (0)