diff --git a/.github/PR-PREVIEW.md b/.github/PR-PREVIEW.md new file mode 100644 index 0000000..b7d64d8 --- /dev/null +++ b/.github/PR-PREVIEW.md @@ -0,0 +1,51 @@ +# PR docs preview setup + +This repository publishes docs previews for pull requests under: + +- `https://psmodule.io/docs/previews/pr-/` + +## What the workflow does + +1. On PR open/reopen/synchronize: + - builds docs with preview-specific `site_url`, + - updates `previews/pr-/` 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-`). +2. On PR close (merge or abandon): + - removes `previews/pr-/` 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 | diff --git a/.github/actions/update-index/src/main.ps1 b/.github/actions/update-index/src/main.ps1 index dbcdf25..f79c5a4 100644 --- a/.github/actions/update-index/src/main.ps1 +++ b/.github/actions/update-index/src/main.ps1 @@ -1,4 +1,23 @@ -Import-Module -Name (Join-Path $PSScriptRoot 'Helper.psm1') +#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 + ./main.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]" diff --git a/.github/actions/update-index/src/Helper.psm1 b/.github/actions/update-index/src/update-index.Helpers.psm1 similarity index 100% rename from .github/actions/update-index/src/Helper.psm1 rename to .github/actions/update-index/src/update-index.Helpers.psm1 diff --git a/.github/scripts/docs/Cleanup-PreviewDocs.ps1 b/.github/scripts/docs/Cleanup-PreviewDocs.ps1 new file mode 100644 index 0000000..80bb716 --- /dev/null +++ b/.github/scripts/docs/Cleanup-PreviewDocs.ps1 @@ -0,0 +1,96 @@ +#Requires -Version 7.0 + +<# + .SYNOPSIS + Removes pull request preview artifacts and related environment state. + + .DESCRIPTION + Deletes previews/pr- 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 = "`n🧹 Preview removed: $PreviewUrl" +Write-IssueComment -Repository $Repository -IssueNumber $PullRequestNumber -Marker '' -Body $commentBody diff --git a/.github/scripts/docs/Publish-LiveDocs.ps1 b/.github/scripts/docs/Publish-LiveDocs.ps1 new file mode 100644 index 0000000..a9f2464 --- /dev/null +++ b/.github/scripts/docs/Publish-LiveDocs.ps1 @@ -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' diff --git a/.github/scripts/docs/Publish-PreviewDocs.ps1 b/.github/scripts/docs/Publish-PreviewDocs.ps1 new file mode 100644 index 0000000..5932c8d --- /dev/null +++ b/.github/scripts/docs/Publish-PreviewDocs.ps1 @@ -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- 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 = "`n✅ Preview is ready: $PreviewUrl" +$env:GH_TOKEN = $Token +Write-IssueComment -Repository $Repository -IssueNumber $PullRequestNumber -Marker '' -Body $commentBody + +Write-WorkflowOutput -Name 'url' -Value $PreviewUrl diff --git a/.github/scripts/docs/Set-ZensicalSiteUrl.ps1 b/.github/scripts/docs/Set-ZensicalSiteUrl.ps1 new file mode 100644 index 0000000..b73ad43 --- /dev/null +++ b/.github/scripts/docs/Set-ZensicalSiteUrl.ps1 @@ -0,0 +1,29 @@ +#Requires -Version 7.0 + +<# + .SYNOPSIS + Sets the site_url field in a Zensical configuration file. + + .DESCRIPTION + Replaces the existing top-level site_url assignment in the provided + zensical.toml file so workflow builds use the expected canonical URL. + + .EXAMPLE + ./Set-ZensicalSiteUrl.ps1 -ConfigPath "$PWD/src/zensical.toml" -SiteUrl "https://psmodule.io/docs/previews/pr-42/" +#> +[CmdletBinding()] +param( + # Full path to the Zensical config file. + [Parameter(Mandatory = $true)] + [string]$ConfigPath, + # Canonical URL to write to site_url. + [Parameter(Mandatory = $true)] + [string]$SiteUrl +) + +Set-StrictMode -Version Latest +$ErrorActionPreference = 'Stop' + +$content = Get-Content -LiteralPath $ConfigPath -Raw +$updated = $content -replace '(?m)^site_url = ".*"$', "site_url = `"$SiteUrl`"" +Set-Content -LiteralPath $ConfigPath -Value $updated diff --git a/.github/scripts/docs/Shared.ps1 b/.github/scripts/docs/Shared.ps1 new file mode 100644 index 0000000..6a8ff36 --- /dev/null +++ b/.github/scripts/docs/Shared.ps1 @@ -0,0 +1,89 @@ +Set-StrictMode -Version Latest +$ErrorActionPreference = 'Stop' + +function Invoke-Git { + <# + .SYNOPSIS + Executes a git command and optionally allows non-zero exits. + #> + param( + [Parameter(Mandatory = $true)] + [string[]]$Arguments, + [switch]$AllowFailure + ) + + & git @Arguments + $exitCode = $LASTEXITCODE + if ($exitCode -ne 0 -and -not $AllowFailure) { + throw "git $($Arguments -join ' ') failed with exit code $exitCode." + } + + return $exitCode +} + +function Write-WorkflowOutput { + <# + .SYNOPSIS + Writes a named value to the GitHub Actions output file. + #> + param( + [Parameter(Mandatory = $true)] + [string]$Name, + [Parameter(Mandatory = $true)] + [string]$Value + ) + + if (-not $env:GITHUB_OUTPUT) { + throw 'GITHUB_OUTPUT is not defined.' + } + + "$Name=$Value" >> $env:GITHUB_OUTPUT +} + +function Invoke-Gh { + <# + .SYNOPSIS + Executes a gh command and optionally allows non-zero exits. + #> + param( + [Parameter(Mandatory = $true)] + [string[]]$Arguments, + [switch]$AllowFailure + ) + + $output = & gh @Arguments + $exitCode = $LASTEXITCODE + if ($exitCode -ne 0 -and -not $AllowFailure) { + throw "gh $($Arguments -join ' ') failed with exit code $exitCode." + } + + return $output +} + +function Write-IssueComment { + <# + .SYNOPSIS + Creates or updates a marker-based issue/PR comment. + #> + param( + [Parameter(Mandatory = $true)] + [string]$Repository, + [Parameter(Mandatory = $true)] + [int]$IssueNumber, + [Parameter(Mandatory = $true)] + [string]$Marker, + [Parameter(Mandatory = $true)] + [string]$Body + ) + + $commentsJson = Invoke-Gh -Arguments @('api', "repos/$Repository/issues/$IssueNumber/comments?per_page=100") + $comments = @($commentsJson | ConvertFrom-Json) + $existing = $comments | Where-Object { $_.body -like "*$Marker*" } | Select-Object -First 1 + + if ($null -ne $existing) { + Invoke-Gh -Arguments @('api', '--method', 'PATCH', "repos/$Repository/issues/comments/$($existing.id)", '-f', "body=$Body") | Out-Null + return + } + + Invoke-Gh -Arguments @('api', '--method', 'POST', "repos/$Repository/issues/$IssueNumber/comments", '-f', "body=$Body") | Out-Null +} diff --git a/.github/workflows/Docs.yml b/.github/workflows/Docs.yml index 1cd4c08..cda9472 100644 --- a/.github/workflows/Docs.yml +++ b/.github/workflows/Docs.yml @@ -9,16 +9,21 @@ on: - main paths: - src/** + - .github/scripts/** - .github/actions/update-index/** - .github/workflows/Docs.yml - - .github/workflows/Update-Index.yml pull_request: + types: + - opened + - reopened + - synchronize + - closed branches: - main paths: - src/** + - .github/scripts/** - .github/workflows/Docs.yml - - .github/workflows/Update-Index.yml concurrency: group: ${{ github.workflow }}-${{ github.ref }} @@ -30,7 +35,7 @@ permissions: jobs: lint: name: Lint - if: github.event_name == 'pull_request' + if: github.event_name == 'pull_request' && github.event.action != 'closed' runs-on: ubuntu-24.04 permissions: contents: read @@ -59,17 +64,29 @@ jobs: VALIDATE_YAML_PRETTIER: false VALIDATE_HTML_PRETTIER: false - build: - name: Build + publish: + name: Publish + if: github.event_name != 'pull_request' runs-on: ubuntu-24.04 + environment: + name: github-pages + url: https://psmodule.io/docs/ permissions: - contents: read + contents: write steps: - name: Checkout repository uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 with: persist-credentials: false + - name: Create GitHub App token + id: app-token + uses: actions/create-github-app-token@fee1f7d63c2ff003460e3d139729b119787bc349 # v2 + with: + app-id: ${{ secrets.SCRIBBLER_BOT_CLIENT_ID }} + private-key: ${{ secrets.SCRIBBLER_BOT_PRIVATE_KEY }} + permission-contents: write + - name: Update index uses: ./.github/actions/update-index with: @@ -88,27 +105,128 @@ jobs: run: zensical build --clean working-directory: src - - name: Upload pages artifact - uses: actions/upload-pages-artifact@fc324d3547104276b827a68afc52ff2a11cc49c9 # v5.0.0 - with: - path: src/site + - name: Publish live docs + id: publish-live + shell: pwsh + env: + REPOSITORY: ${{ github.repository }} + APP_TOKEN: ${{ steps.app-token.outputs.token }} + COMMIT_SHA: ${{ github.sha }} + run: | + ./.github/scripts/docs/Publish-LiveDocs.ps1 ` + -Repository $env:REPOSITORY ` + -Token $env:APP_TOKEN ` + -BuildDirectory "$PWD/src/site" ` + -CommitSha $env:COMMIT_SHA - publish: - name: Publish - needs: [build, lint] - if: github.event_name != 'pull_request' + preview: + name: Preview + if: github.event_name == 'pull_request' && github.event.action != 'closed' + needs: [lint] runs-on: ubuntu-24.04 environment: - name: github-pages - url: ${{ steps.deployment.outputs.page_url }} + name: pr-preview-${{ github.event.number }} + url: ${{ steps.preview-url.outputs.url }} permissions: - contents: read - pages: write # deploy to GitHub Pages - id-token: write # OIDC token for actions/deploy-pages + contents: write + deployments: write + issues: write + env: + PREVIEW_URL: https://psmodule.io/docs/previews/pr-${{ github.event.number }}/ steps: - - name: Configure pages - uses: actions/configure-pages@45bfe0192ca1faeb007ade9deae92b16b8254a0d # v6.0.0 + - name: Checkout repository + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + with: + persist-credentials: false - - name: Deploy to GitHub Pages - uses: actions/deploy-pages@cd2ce8fcbc39b97be8ca5fce6e763baed58fa128 # v5.0.0 - id: deployment + - name: Create GitHub App token + id: app-token + uses: actions/create-github-app-token@fee1f7d63c2ff003460e3d139729b119787bc349 # v2 + with: + app-id: ${{ secrets.SCRIBBLER_BOT_CLIENT_ID }} + private-key: ${{ secrets.SCRIBBLER_BOT_PRIVATE_KEY }} + permission-contents: write + permission-issues: write + + - name: Update index + uses: ./.github/actions/update-index + with: + ClientID: ${{ secrets.SCRIBBLER_BOT_CLIENT_ID }} # zizmor: ignore[secrets-outside-env] + PrivateKey: ${{ secrets.SCRIBBLER_BOT_PRIVATE_KEY }} # zizmor: ignore[secrets-outside-env] + + - name: Set up Python + uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6.3.0 + with: + python-version: 3.x + + - name: Install Zensical + run: pip install zensical + + - name: Build preview site + shell: pwsh + env: + PREVIEW_SITE_URL: ${{ env.PREVIEW_URL }} + run: | + ./.github/scripts/docs/Set-ZensicalSiteUrl.ps1 -ConfigPath "$PWD/zensical.toml" -SiteUrl $env:PREVIEW_SITE_URL + zensical build --clean + working-directory: src + + - name: Publish preview docs + id: preview-url + shell: pwsh + env: + REPOSITORY: ${{ github.repository }} + APP_TOKEN: ${{ steps.app-token.outputs.token }} + PR_NUMBER: ${{ github.event.number }} + PREVIEW_SITE_URL: ${{ env.PREVIEW_URL }} + run: | + ./.github/scripts/docs/Publish-PreviewDocs.ps1 ` + -Repository $env:REPOSITORY ` + -Token $env:APP_TOKEN ` + -PullRequestNumber $env:PR_NUMBER ` + -PreviewUrl $env:PREVIEW_SITE_URL ` + -BuildDirectory "$PWD/src/site" + + preview-cleanup: + name: Preview cleanup + if: github.event_name == 'pull_request' && github.event.action == 'closed' + runs-on: ubuntu-24.04 + permissions: + contents: write + deployments: write + issues: write + env: + PREVIEW_URL: https://psmodule.io/docs/previews/pr-${{ github.event.number }}/ + PREVIEW_ENVIRONMENT: pr-preview-${{ github.event.number }} + steps: + - name: Checkout repository + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + with: + persist-credentials: false + + - name: Create GitHub App token + id: app-token + uses: actions/create-github-app-token@fee1f7d63c2ff003460e3d139729b119787bc349 # v2 + with: + app-id: ${{ secrets.SCRIBBLER_BOT_CLIENT_ID }} + private-key: ${{ secrets.SCRIBBLER_BOT_PRIVATE_KEY }} + permission-administration: write + permission-contents: write + permission-deployments: write + permission-issues: write + + - name: Cleanup preview resources + shell: pwsh + env: + REPOSITORY: ${{ github.repository }} + APP_TOKEN: ${{ steps.app-token.outputs.token }} + PR_NUMBER: ${{ github.event.number }} + PREVIEW_SITE_URL: ${{ env.PREVIEW_URL }} + PREVIEW_ENV: ${{ env.PREVIEW_ENVIRONMENT }} + run: | + ./.github/scripts/docs/Cleanup-PreviewDocs.ps1 ` + -Repository $env:REPOSITORY ` + -Token $env:APP_TOKEN ` + -PullRequestNumber $env:PR_NUMBER ` + -PreviewUrl $env:PREVIEW_SITE_URL ` + -EnvironmentName $env:PREVIEW_ENV diff --git a/README.md b/README.md index 3557d2f..0b44564 100644 --- a/README.md +++ b/README.md @@ -3,3 +3,5 @@ This repository contains the documentation for the [PSModule](https://psmodule.io) development framework. Visit [psmodule.io/docs](https://psmodule.io/docs) to read the documentation created in this repository. + +PRs automatically publish a docs preview at `https://psmodule.io/docs/previews/pr-/` and update the same preview URL as new commits are pushed. The preview is removed when the PR closes (merged or abandoned). See [.github/PR-PREVIEW.md](.github/PR-PREVIEW.md) for setup and required bot permissions.