-
Notifications
You must be signed in to change notification settings - Fork 0
Add PR docs preview lifecycle via gh-pages #75
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Draft
Marius Storhaug (MariusStorhaug)
wants to merge
9
commits into
main
Choose a base branch
from
pr-docs-previews
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Draft
Changes from 8 commits
Commits
Show all changes
9 commits
Select commit
Hold shift + click to select a range
4b5287d
Add PR preview lifecycle workflow
MariusStorhaug cd5b0ac
Document preview setup and permissions
MariusStorhaug 1643c9e
Extract docs workflow logic into scripts
MariusStorhaug 5674696
Switch docs deploy to app-only gh-pages pushes
MariusStorhaug bfd59cf
Add preview lifecycle flow diagram
MariusStorhaug 18fe88d
Align scripts with MSX PowerShell guidance
MariusStorhaug 723ac80
Fix workflow lint and script analyzer findings
MariusStorhaug 03df0e3
Resolve remaining PowerShell lint warning
MariusStorhaug 4fa42e3
Rename Invoke-UpdateIndex.ps1 to main.ps1 per action entry point stan…
Copilot File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,51 @@ | ||
| # PR docs preview setup | ||
|
|
||
| This repository publishes docs previews for pull requests under: | ||
|
|
||
| - `https://psmodule.io/docs/previews/pr-<number>/` | ||
|
|
||
| ## What the workflow does | ||
|
|
||
| 1. On PR open/reopen/synchronize: | ||
| - builds docs with preview-specific `site_url`, | ||
| - updates `previews/pr-<number>/` content by pushing directly to `gh-pages` as the Scribbler app, | ||
| - comments on the source PR with the preview URL, | ||
| - reports the preview URL through a named environment (`pr-preview-<number>`). | ||
| 2. On PR close (merge or abandon): | ||
| - removes `previews/pr-<number>/` by pushing directly to `gh-pages` as the Scribbler app, | ||
| - deletes all preview deployments and the preview environment. | ||
|
|
||
| ## Flow diagram | ||
|
|
||
| ```mermaid | ||
| flowchart TD | ||
| A[Contributor opens or updates PR to main] --> B[Docs workflow builds preview site] | ||
| B --> C[Scribbler app token pushes preview files to gh-pages under previews/pr-N] | ||
| C --> D[Workflow sets environment pr-preview-N with preview URL] | ||
| D --> E[Workflow comments on PR with preview link] | ||
| E --> F[Contributor iterates on PR] | ||
| F --> B | ||
| G[PR closed: merged or abandoned] --> H[Cleanup job runs] | ||
| H --> I[Scribbler app token removes previews/pr-N from gh-pages] | ||
| I --> J[Workflow deletes preview deployments + environment] | ||
| J --> K[Workflow updates PR comment: preview removed] | ||
| ``` | ||
|
|
||
| ## Required repository configuration | ||
|
|
||
| 1. Ensure `gh-pages` branch exists. | ||
| 2. Configure GitHub Pages to publish from `gh-pages`. | ||
| 3. Protect `gh-pages` and restrict push access so **only Scribbler bot app** can push. | ||
| 4. In the `gh-pages` branch protection/ruleset, add **Scribbler bot app** as the only actor allowed to bypass required pull requests and any required status checks for that branch. | ||
|
|
||
| ## Scribbler GitHub App permissions | ||
|
|
||
| The app needs the following repository permissions: | ||
|
|
||
| | Permission | Access | Why | | ||
| | --- | --- | --- | | ||
| | Metadata | Read | Required baseline for API access | | ||
| | Contents | Read & write | Push docs and preview content directly to `gh-pages` | | ||
| | Issues | Read & write | Post and update preview comments on PR threads | | ||
| | Deployments | Read & write | Deactivate and delete preview deployments | | ||
| | Administration | Read & write | Delete per-PR environments during cleanup | |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,42 @@ | ||
| #Requires -Version 7.0 | ||
|
|
||
| <# | ||
| .SYNOPSIS | ||
| Updates generated index content for the docs repository. | ||
|
|
||
| .DESCRIPTION | ||
| Orchestrates the update-index action flow by collecting organization repository | ||
| metadata and regenerating module catalog documentation artifacts. | ||
|
|
||
| .EXAMPLE | ||
| ./Invoke-UpdateIndex.ps1 | ||
| #> | ||
| [CmdletBinding()] | ||
| param() | ||
|
|
||
| Set-StrictMode -Version Latest | ||
| $ErrorActionPreference = 'Stop' | ||
|
|
||
| Import-Module -Name (Join-Path $PSScriptRoot 'update-index.Helpers.psm1') | ||
|
|
||
| LogGroup 'Initialize update-index run' { | ||
| Write-Host "Starting update-index in [$PSScriptRoot]" | ||
| } | ||
|
|
||
| LogGroup 'Collect repositories' { | ||
| $repos = Show-RepoList | ||
| Write-Host "Repository collection complete: $($repos.Count) records" | ||
| } | ||
|
|
||
| LogGroup 'Skipped generators' { | ||
| Write-Host 'Update-ActionList is currently disabled in Invoke-UpdateIndex.ps1' | ||
| Write-Host 'Update-FunctionAppList is currently disabled in Invoke-UpdateIndex.ps1' | ||
| } | ||
|
|
||
| LogGroup 'Update module catalog docs' { | ||
| Update-ModuleList -Repos $repos | ||
| } | ||
|
|
||
| LogGroup 'Finalize update-index run' { | ||
| Write-Host 'update-index run completed' | ||
| } |
This file was deleted.
Oops, something went wrong.
File renamed without changes.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,96 @@ | ||
| #Requires -Version 7.0 | ||
|
|
||
| <# | ||
| .SYNOPSIS | ||
| Removes pull request preview artifacts and related environment state. | ||
|
|
||
| .DESCRIPTION | ||
| Deletes previews/pr-<number> from gh-pages, deactivates and removes preview | ||
| deployments, deletes the preview environment, and updates the PR comment. | ||
|
|
||
| .EXAMPLE | ||
| ./Cleanup-PreviewDocs.ps1 ` | ||
| -Repository "PSModule/docs" ` | ||
| -Token $token ` | ||
| -PullRequestNumber 42 ` | ||
| -PreviewUrl "https://psmodule.io/docs/previews/pr-42/" ` | ||
| -EnvironmentName "pr-preview-42" | ||
| #> | ||
| [CmdletBinding()] | ||
| param( | ||
| # Repository in owner/name format. | ||
| [Parameter(Mandatory = $true)] | ||
| [string]$Repository, | ||
| # GitHub App token used for authenticated git and gh operations. | ||
| [Parameter(Mandatory = $true)] | ||
| [string]$Token, | ||
| # Pull request number used for preview path and comment targeting. | ||
| [Parameter(Mandatory = $true)] | ||
| [int]$PullRequestNumber, | ||
| # Public preview URL for PR comments. | ||
| [Parameter(Mandatory = $true)] | ||
| [string]$PreviewUrl, | ||
| # Environment name to clean up from repository environments. | ||
| [Parameter(Mandatory = $true)] | ||
| [string]$EnvironmentName, | ||
| # Working directory used to clone gh-pages. | ||
| [string]$PagesDirectory = '_pages', | ||
| # Branch used as the Pages source branch. | ||
| [string]$BaseBranch = 'gh-pages' | ||
| ) | ||
|
|
||
| Set-StrictMode -Version Latest | ||
| $ErrorActionPreference = 'Stop' | ||
|
|
||
| . "$PSScriptRoot/Shared.ps1" | ||
|
|
||
| $clonedPages = $true | ||
| Invoke-Git -Arguments @( | ||
| 'clone', | ||
| '--no-tags', | ||
| '--depth', '1', | ||
| '--branch', $BaseBranch, | ||
| "https://x-access-token:$Token@github.com/$Repository.git", | ||
| $PagesDirectory | ||
| ) -AllowFailure | Out-Null | ||
|
|
||
| if (-not (Test-Path -LiteralPath $PagesDirectory -PathType Container)) { | ||
| $clonedPages = $false | ||
| } | ||
|
|
||
| if ($clonedPages) { | ||
| $previewDirectory = Join-Path $PagesDirectory "previews/pr-$PullRequestNumber" | ||
| if (Test-Path -LiteralPath $previewDirectory) { | ||
| Remove-Item -LiteralPath $previewDirectory -Recurse -Force | ||
| } | ||
|
|
||
| Invoke-Git -Arguments @('-C', $PagesDirectory, 'config', 'user.name', 'scribbler-bot[bot]') | ||
| Invoke-Git -Arguments @('-C', $PagesDirectory, 'config', 'user.email', 'scribe@psmodule.io') | ||
| Invoke-Git -Arguments @('-C', $PagesDirectory, 'add', '-A') | ||
|
|
||
| $status = (& git -C $PagesDirectory status --porcelain) | ||
| if (-not [string]::IsNullOrWhiteSpace($status)) { | ||
| Invoke-Git -Arguments @('-C', $PagesDirectory, 'commit', '-m', "Remove docs preview for PR #$PullRequestNumber") | ||
| Invoke-Git -Arguments @('-C', $PagesDirectory, 'push', 'origin', "HEAD:refs/heads/$BaseBranch") | ||
| } | ||
| } | ||
|
|
||
| $env:GH_TOKEN = $Token | ||
| $deploymentsJson = Invoke-Gh -Arguments @('api', "repos/$Repository/deployments?environment=$EnvironmentName&per_page=100") | ||
| $deployments = @($deploymentsJson | ConvertFrom-Json) | ||
|
|
||
| foreach ($deployment in $deployments) { | ||
| Invoke-Gh -Arguments @('api', '--method', 'POST', "repos/$Repository/deployments/$($deployment.id)/statuses", '-f', 'state=inactive') | Out-Null | ||
| Invoke-Gh -Arguments @('api', '--method', 'DELETE', "repos/$Repository/deployments/$($deployment.id)") | Out-Null | ||
| } | ||
|
|
||
| Invoke-Gh -Arguments @('api', '--method', 'DELETE', "repos/$Repository/environments/$EnvironmentName") -AllowFailure | Out-Null | ||
| if ($LASTEXITCODE -ne 0) { | ||
| Invoke-Gh -Arguments @('api', "repos/$Repository/environments/$EnvironmentName") -AllowFailure | Out-Null | ||
| if ($LASTEXITCODE -eq 0) { | ||
| throw "Failed to delete environment '$EnvironmentName'." | ||
| } | ||
| } | ||
|
|
||
| $commentBody = "<!-- docs-pr-preview -->`n🧹 Preview removed: $PreviewUrl" | ||
| Write-IssueComment -Repository $Repository -IssueNumber $PullRequestNumber -Marker '<!-- docs-pr-preview -->' -Body $commentBody |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,84 @@ | ||
| #Requires -Version 7.0 | ||
|
|
||
| <# | ||
| .SYNOPSIS | ||
| Publishes live docs content to the gh-pages branch. | ||
|
|
||
| .DESCRIPTION | ||
| Clones gh-pages, replaces live content while keeping previews, and pushes | ||
| the updated branch using the GitHub App token identity. | ||
|
|
||
| .EXAMPLE | ||
| ./Publish-LiveDocs.ps1 ` | ||
| -Repository "PSModule/docs" ` | ||
| -Token $token ` | ||
| -BuildDirectory "$PWD/src/site" ` | ||
| -CommitSha $env:GITHUB_SHA | ||
| #> | ||
| [CmdletBinding()] | ||
| param( | ||
| # Repository in owner/name format. | ||
| [Parameter(Mandatory = $true)] | ||
| [string]$Repository, | ||
| # GitHub App token used for authenticated git and gh operations. | ||
| [Parameter(Mandatory = $true)] | ||
| [string]$Token, | ||
| # Full path to the built static site directory. | ||
| [Parameter(Mandatory = $true)] | ||
| [string]$BuildDirectory, | ||
| # Commit SHA used in the publish commit message. | ||
| [Parameter(Mandatory = $true)] | ||
| [string]$CommitSha, | ||
| # Working directory used to clone gh-pages. | ||
| [string]$PagesDirectory = '_pages', | ||
| # Branch used as the Pages source branch. | ||
| [string]$BaseBranch = 'gh-pages' | ||
| ) | ||
|
|
||
| Set-StrictMode -Version Latest | ||
| $ErrorActionPreference = 'Stop' | ||
|
|
||
| . "$PSScriptRoot/Shared.ps1" | ||
|
|
||
| if (-not (Test-Path -LiteralPath $BuildDirectory -PathType Container)) { | ||
| throw "Build directory '$BuildDirectory' does not exist." | ||
| } | ||
|
|
||
| Invoke-Git -Arguments @( | ||
| 'clone', | ||
| '--no-tags', | ||
| '--depth', '1', | ||
| '--branch', $BaseBranch, | ||
| "https://x-access-token:$Token@github.com/$Repository.git", | ||
| $PagesDirectory | ||
| ) -AllowFailure | Out-Null | ||
|
|
||
| if (-not (Test-Path -LiteralPath $PagesDirectory -PathType Container)) { | ||
| throw "$BaseBranch branch is required for branch-based deployment." | ||
| } | ||
|
|
||
| Get-ChildItem -LiteralPath $PagesDirectory -Force | | ||
| Where-Object { $_.Name -notin @('.git', 'previews') } | | ||
| Remove-Item -Recurse -Force | ||
|
|
||
| Get-ChildItem -LiteralPath $BuildDirectory -Force | | ||
| ForEach-Object { | ||
| Copy-Item -LiteralPath $_.FullName -Destination $PagesDirectory -Recurse -Force | ||
| } | ||
|
|
||
| New-Item -Path (Join-Path $PagesDirectory '.nojekyll') -ItemType File -Force | Out-Null | ||
|
|
||
| Invoke-Git -Arguments @('-C', $PagesDirectory, 'config', 'user.name', 'scribbler-bot[bot]') | ||
| Invoke-Git -Arguments @('-C', $PagesDirectory, 'config', 'user.email', 'scribe@psmodule.io') | ||
| Invoke-Git -Arguments @('-C', $PagesDirectory, 'add', '-A') | ||
|
|
||
| $status = (& git -C $PagesDirectory status --porcelain) | ||
| if ([string]::IsNullOrWhiteSpace($status)) { | ||
| Write-WorkflowOutput -Name 'has_changes' -Value 'false' | ||
| exit 0 | ||
| } | ||
|
|
||
| Invoke-Git -Arguments @('-C', $PagesDirectory, 'commit', '-m', "Deploy docs from $CommitSha") | ||
| Invoke-Git -Arguments @('-C', $PagesDirectory, 'push', 'origin', "HEAD:refs/heads/$BaseBranch") | ||
|
|
||
| Write-WorkflowOutput -Name 'has_changes' -Value 'true' |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,87 @@ | ||
| #Requires -Version 7.0 | ||
|
|
||
| <# | ||
| .SYNOPSIS | ||
| Publishes pull request preview docs content to gh-pages. | ||
|
|
||
| .DESCRIPTION | ||
| Writes the PR preview build output into previews/pr-<number> on gh-pages, | ||
| pushes branch updates, and ensures the preview comment is present on the PR. | ||
|
|
||
| .EXAMPLE | ||
| ./Publish-PreviewDocs.ps1 ` | ||
| -Repository "PSModule/docs" ` | ||
| -Token $token ` | ||
| -PullRequestNumber 42 ` | ||
| -PreviewUrl "https://psmodule.io/docs/previews/pr-42/" ` | ||
| -BuildDirectory "$PWD/src/site" | ||
| #> | ||
| [CmdletBinding()] | ||
| param( | ||
| # Repository in owner/name format. | ||
| [Parameter(Mandatory = $true)] | ||
| [string]$Repository, | ||
| # GitHub App token used for authenticated git and gh operations. | ||
| [Parameter(Mandatory = $true)] | ||
| [string]$Token, | ||
| # Pull request number used for preview path and comment targeting. | ||
| [Parameter(Mandatory = $true)] | ||
| [int]$PullRequestNumber, | ||
| # Public preview URL for PR comments and environment metadata. | ||
| [Parameter(Mandatory = $true)] | ||
| [string]$PreviewUrl, | ||
| # Full path to the built static site directory. | ||
| [Parameter(Mandatory = $true)] | ||
| [string]$BuildDirectory, | ||
| # Working directory used to clone gh-pages. | ||
| [string]$PagesDirectory = '_pages', | ||
| # Branch used as the Pages source branch. | ||
| [string]$BaseBranch = 'gh-pages' | ||
| ) | ||
|
|
||
| Set-StrictMode -Version Latest | ||
| $ErrorActionPreference = 'Stop' | ||
|
|
||
| . "$PSScriptRoot/Shared.ps1" | ||
|
|
||
| if (-not (Test-Path -LiteralPath $BuildDirectory -PathType Container)) { | ||
| throw "Build directory '$BuildDirectory' does not exist." | ||
| } | ||
|
|
||
| Invoke-Git -Arguments @( | ||
| 'clone', | ||
| '--no-tags', | ||
| '--depth', '1', | ||
| '--branch', $BaseBranch, | ||
| "https://x-access-token:$Token@github.com/$Repository.git", | ||
| $PagesDirectory | ||
| ) | ||
|
|
||
| $previewDirectory = Join-Path $PagesDirectory "previews/pr-$PullRequestNumber" | ||
| if (Test-Path -LiteralPath $previewDirectory) { | ||
| Remove-Item -LiteralPath $previewDirectory -Recurse -Force | ||
| } | ||
|
|
||
| New-Item -Path $previewDirectory -ItemType Directory -Force | Out-Null | ||
| Get-ChildItem -LiteralPath $BuildDirectory -Force | | ||
| ForEach-Object { | ||
| Copy-Item -LiteralPath $_.FullName -Destination $previewDirectory -Recurse -Force | ||
| } | ||
|
|
||
| New-Item -Path (Join-Path $PagesDirectory '.nojekyll') -ItemType File -Force | Out-Null | ||
|
|
||
| Invoke-Git -Arguments @('-C', $PagesDirectory, 'config', 'user.name', 'scribbler-bot[bot]') | ||
| Invoke-Git -Arguments @('-C', $PagesDirectory, 'config', 'user.email', 'scribe@psmodule.io') | ||
| Invoke-Git -Arguments @('-C', $PagesDirectory, 'add', '-A') | ||
|
|
||
| $status = (& git -C $PagesDirectory status --porcelain) | ||
| if (-not [string]::IsNullOrWhiteSpace($status)) { | ||
| Invoke-Git -Arguments @('-C', $PagesDirectory, 'commit', '-m', "Update docs preview for PR #$PullRequestNumber") | ||
| Invoke-Git -Arguments @('-C', $PagesDirectory, 'push', 'origin', "HEAD:refs/heads/$BaseBranch") | ||
| } | ||
|
|
||
| $commentBody = "<!-- docs-pr-preview -->`n✅ Preview is ready: $PreviewUrl" | ||
| $env:GH_TOKEN = $Token | ||
| Write-IssueComment -Repository $Repository -IssueNumber $PullRequestNumber -Marker '<!-- docs-pr-preview -->' -Body $commentBody | ||
|
|
||
| Write-WorkflowOutput -Name 'url' -Value $PreviewUrl |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Copilot Standard says all actions have main.ps1 as the entry point. The action name carries the name of what it does, the controller is in main.ps1.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Renamed
Invoke-UpdateIndex.ps1tomain.ps1and updated the path reference inaction.ymlaccordingly.