From 991c66454944d94fb36892bf043b0541411fdf8a Mon Sep 17 00:00:00 2001 From: Joe Fusco Date: Thu, 20 Aug 2026 17:13:03 -0500 Subject: [PATCH] chore: remove the Playground preview workflows These three workflows have never worked. Playground Preview Publish has failed on all 8 of its runs, and the causes date to 52c0077, the commit that introduced them -- there was never a working version to regress from. Because publish runs on `workflow_run`, its failures never appeared in `gh pr checks` and never gated a PR, so it sat broken unnoticed for the repo's entire history. The breakage is structural rather than a small fix: - `test -f artifacts/release-assets/` tests a directory, which is always false under `bash -e`; this kills the job ~10s in, before anything else runs. - Two of the three release assets (a "seeder" and a "helper") were scaffolded as bare paths with the filenames never filled in, and no such files exist anywhere in the repo. The build stages only sync-storage.zip. - blueprint-40.json, transformed on line 141 and advertised in the sticky comment as a "40 demo users" variant, was never created. - blueprint.json has no writeFile steps, so the seeder/helper half of the jq transform is a no-op regardless, and its two `endswith("/")` branches are identical, leaving the elif unreachable. Nothing depends on them: no Playground job is a required status check, nothing outside the three files references them, and no preview-pr-* releases or tags were ever produced. Deleting is honest about the state of things; if per-PR previews are wanted later, they are better rebuilt than repaired. blueprint.json stays -- the README badge points Playground at it directly via raw.githubusercontent.com and never involved these workflows. --- .../workflows/playground-preview-cleanup.yml | 33 --- .../workflows/playground-preview-publish.yml | 199 ------------------ .github/workflows/playground-preview.yml | 93 -------- 3 files changed, 325 deletions(-) delete mode 100644 .github/workflows/playground-preview-cleanup.yml delete mode 100644 .github/workflows/playground-preview-publish.yml delete mode 100644 .github/workflows/playground-preview.yml diff --git a/.github/workflows/playground-preview-cleanup.yml b/.github/workflows/playground-preview-cleanup.yml deleted file mode 100644 index 5a44b8a..0000000 --- a/.github/workflows/playground-preview-cleanup.yml +++ /dev/null @@ -1,33 +0,0 @@ -name: Playground Preview Cleanup - -# Deletes the per-PR preview release + tag when a PR is closed. -# -# Uses `pull_request_target` so cleanup also fires for PRs from forks -# (the token from `pull_request: closed` is read-only on forks). This -# workflow never checks out PR code and only calls the release API -# with values from the GitHub event payload, so `pull_request_target` -# is safe here. - -on: - pull_request_target: # zizmor: ignore[dangerous-triggers] never checks out PR code; only calls the release API with event-payload values - types: [closed] - -permissions: - contents: write - -jobs: - cleanup: - runs-on: ubuntu-24.04 - if: ${{ !startsWith(github.head_ref, 'release-please--') }} - steps: - - name: Delete preview release and tag - env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - GH_REPO: ${{ github.repository }} - PR_NUMBER: ${{ github.event.pull_request.number }} - run: | - set -euo pipefail - TAG="preview-pr-${PR_NUMBER}" - # Tolerate "release not found" — the build may have failed or - # been skipped, leaving nothing to clean up. - gh release delete "$TAG" --yes --cleanup-tag 2>/dev/null || true diff --git a/.github/workflows/playground-preview-publish.yml b/.github/workflows/playground-preview-publish.yml deleted file mode 100644 index 0d17c0e..0000000 --- a/.github/workflows/playground-preview-publish.yml +++ /dev/null @@ -1,199 +0,0 @@ -name: Playground Preview Publish - -# Runs in the base-repo context (via `workflow_run`) so it has write -# access even for PRs from forks. Downloads the build artifacts from -# the triggering workflow, publishes them as a prerelease, and posts -# (or updates) the sticky Playground comment on the PR. -# -# Security model: every PR-derived input is untrusted. -# - `pr-meta.json` is parsed with jq and regex-validated; we never -# `source` it as shell. -# - The blueprint templates we encode into Playground URLs come from -# the base ref (this checkout), not from the PR — only the plugin -# and seeder URLs are rewritten in, and both point exclusively at -# github.com/${{ github.repository }} under our control, so a fork -# PR cannot direct Playground at attacker-controlled URLs through -# this workflow. - -on: - workflow_run: # zizmor: ignore[dangerous-triggers] security model documented above: PR-derived inputs are regex-validated, never sourced as shell, and URLs point only at this repo - workflows: ["Playground Preview"] - types: [completed] - -concurrency: - group: ${{ github.workflow }}-${{ github.event.workflow_run.head_sha }} - cancel-in-progress: true - -permissions: - contents: write - actions: read - pull-requests: write - -jobs: - publish: - if: > - github.event.workflow_run.conclusion == 'success' - && github.event.workflow_run.event == 'pull_request' - && !startsWith(github.event.workflow_run.head_branch, 'release-please--') - runs-on: ubuntu-24.04 - steps: - - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - with: - persist-credentials: false - - - name: Download artifacts from the triggering run - uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 - with: - script: | - const fs = require('fs'); - const list = await github.rest.actions.listWorkflowRunArtifacts({ - owner: context.repo.owner, - repo: context.repo.repo, - run_id: context.payload.workflow_run.id, - }); - for (const name of ['release-assets', 'pr-meta']) { - const found = list.data.artifacts.find(a => a.name === name); - if (!found) { - core.setFailed(`Required artifact "${name}" not found on workflow run.`); - return; - } - const dl = await github.rest.actions.downloadArtifact({ - owner: context.repo.owner, - repo: context.repo.repo, - artifact_id: found.id, - archive_format: 'zip', - }); - fs.writeFileSync(`${name}.zip`, Buffer.from(dl.data)); - } - - - name: Unpack and validate artifacts - run: | - set -euo pipefail - mkdir -p artifacts/release-assets artifacts/pr-meta - unzip -q -o -d artifacts/release-assets release-assets.zip - unzip -q -o -d artifacts/pr-meta pr-meta.zip - test -f artifacts/release-assets/sync-storage.zip - test -f artifacts/release-assets/ - test -f artifacts/release-assets/ - test -f artifacts/pr-meta/pr-meta.json - - # Parse with jq and validate types. Never `source` the artifact. - PR_NUMBER=$(jq -r '.pr_number' artifacts/pr-meta/pr-meta.json) - HEAD_SHA=$(jq -r '.head_sha' artifacts/pr-meta/pr-meta.json) - [[ "$PR_NUMBER" =~ ^[1-9][0-9]*$ ]] \ - || { echo "Invalid PR number in pr-meta: $PR_NUMBER"; exit 1; } - [[ "$HEAD_SHA" =~ ^[0-9a-f]{40}$ ]] \ - || { echo "Invalid SHA in pr-meta: $HEAD_SHA"; exit 1; } - { - echo "PR_NUMBER=$PR_NUMBER" - echo "HEAD_SHA=$HEAD_SHA" - } >> "$GITHUB_ENV" - - - name: Publish prerelease (replaces any prior preview for this PR) - env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - run: | - set -euo pipefail - TAG="preview-pr-${PR_NUMBER}" - # Drop any prior release+tag for this PR so we always serve the - # latest preview from a stable URL. Tolerate first-time absence. - gh release delete "$TAG" --yes --cleanup-tag 2>/dev/null || true - gh release create "$TAG" \ - artifacts/release-assets/sync-storage.zip \ - artifacts/release-assets/ \ - artifacts/release-assets/ \ - --prerelease \ - --target "$HEAD_SHA" \ - --title "PR #${PR_NUMBER} preview" \ - --notes "Auto-built from \`${HEAD_SHA}\` for PR #${PR_NUMBER}. Deleted on PR close." - - - name: Assemble Playground URLs from base-ref blueprints - id: playground - env: - REPO: ${{ github.repository }} - run: | - set -euo pipefail - TAG="preview-pr-${PR_NUMBER}" - # Both URLs resolve to github.com/${REPO} — never to the PR's - # head repository — so a fork PR cannot redirect Playground. - PLUGIN_URL="https://github.com/${REPO}/releases/download/${TAG}/sync-storage.zip" - SEEDER_URL="https://github.com/${REPO}/releases/download/${TAG}/" - HELPER_URL="https://github.com/${REPO}/releases/download/${TAG}/" - - # writeFile steps are matched by trailing path component so the - # seeder and helper get distinct URLs without depending on the - # order they appear in the blueprint. - transform() { - jq --arg plugin "$1" --arg seeder "$2" --arg helper "$3" ' - .steps |= map( - if .step == "installPlugin" and (.pluginData.resource == "url") then - .pluginData.url = $plugin - elif .step == "writeFile" and (.data.resource == "url") then - if (.path | endswith("/")) then .data.url = $seeder - elif (.path | endswith("/")) then .data.url = $helper - else . end - else . end - ) - ' "$4" - } - - BLUEPRINT=$(transform "$PLUGIN_URL" "$SEEDER_URL" "$HELPER_URL" blueprint.json) - BLUEPRINT_40=$(transform "$PLUGIN_URL" "$SEEDER_URL" "$HELPER_URL" blueprint-40.json) - - encode() { printf '%s' "$1" | base64 -w0; } - - { - echo "url=https://playground.wordpress.net/#$(encode "$BLUEPRINT")" - echo "url_40=https://playground.wordpress.net/#$(encode "$BLUEPRINT_40")" - } >> "$GITHUB_OUTPUT" - - - name: Post or update sticky comment - uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 - env: - PR_NUMBER: ${{ env.PR_NUMBER }} - HEAD_SHA: ${{ env.HEAD_SHA }} - PLAYGROUND_URL: ${{ steps.playground.outputs.url }} - PLAYGROUND_URL_40: ${{ steps.playground.outputs.url_40 }} - with: - script: | - const marker = ''; - const url = process.env.PLAYGROUND_URL; - const url40 = process.env.PLAYGROUND_URL_40; - const sha = process.env.HEAD_SHA; - const prNumber = parseInt(process.env.PR_NUMBER, 10); - const badge = 'https://img.shields.io/badge/Open%20in-WordPress%20Playground-3858E9?logo=wordpress&logoColor=white'; - - const body = [ - marker, - '## ▶ Preview in WordPress Playground', - '', - `[![Open in WordPress Playground](${badge})](${url})`, - '', - "Boots a fresh WordPress with this PR's sync-storage build, seeds 5 demo users, and drops you on the dashboard.", - '', - `Stress-test variant: [40 demo users](${url40}) · Built from \`${sha}\`. Auto-updates when you push.`, - ].join('\n'); - - const comments = await github.paginate(github.rest.issues.listComments, { - owner: context.repo.owner, - repo: context.repo.repo, - issue_number: prNumber, - per_page: 100, - }); - - const existing = comments.find(c => c.body && c.body.includes(marker)); - if (existing) { - await github.rest.issues.updateComment({ - owner: context.repo.owner, - repo: context.repo.repo, - comment_id: existing.id, - body, - }); - } else { - await github.rest.issues.createComment({ - owner: context.repo.owner, - repo: context.repo.repo, - issue_number: prNumber, - body, - }); - } diff --git a/.github/workflows/playground-preview.yml b/.github/workflows/playground-preview.yml deleted file mode 100644 index eb8f074..0000000 --- a/.github/workflows/playground-preview.yml +++ /dev/null @@ -1,93 +0,0 @@ -name: Playground Preview - -# Builds `sync-storage.zip` from the PR commit and uploads it -# (plus metadata) as workflow artifacts. The sibling `Playground Preview -# Publish` workflow picks the artifacts up on `workflow_run` and hands -# them to GitHub Releases for hosting — we don't depend on any -# third-party artifact proxy. - -on: - pull_request: - paths-ignore: - - '**.md' - - '.github/**' - - 'tests/**' - workflow_dispatch: - -concurrency: - group: ${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: true - -permissions: - contents: read - -jobs: - build: - runs-on: ubuntu-24.04 - if: ${{ !startsWith(github.head_ref, 'release-please--') }} - steps: - - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - with: - persist-credentials: false - ref: ${{ github.event.pull_request.head.sha }} - - - uses: shivammathur/setup-php@f3e473d116dcccaddc5834248c87452386958240 # v2 - with: - php-version: '8.3' - tools: composer - - - run: composer install --no-dev --no-interaction --optimize-autoloader --no-scripts - - - name: Build sync-storage.zip - run: | - set -euo pipefail - mkdir -p build/sync-storage - rsync -a --exclude-from='.distignore' --exclude='build' ./ build/sync-storage/ - (cd build && zip -qr sync-storage.zip sync-storage/) - - - name: Smoke-test the build - run: | - set -euo pipefail - test -f build/sync-storage/sync-storage.php - find build/sync-storage -name '*.php' -print0 \ - | xargs -0 -n1 -P 4 php -l > /dev/null - - - name: Schema-validate blueprint - run: | - set -euo pipefail - jq -e ' - (.steps | length > 0) - and (.steps | all(.step | IN("login", "installPlugin", "defineWpConfigConsts", "runPHP"))) - and (.steps | map(select(.step == "installPlugin" and (.pluginZipFile.resource // "") == "url")) | length == 1) - and (.steps | map(select(.step == "installPlugin" and (.pluginZipFile.resource // "") == "wordpress.org/plugins")) | length == 2) - ' blueprint.json > /dev/null - - - name: Stage release assets - run: | - set -euo pipefail - mkdir -p release-assets - cp build/sync-storage.zip release-assets/ - - - name: Save PR metadata - env: - PR_NUMBER: ${{ github.event.pull_request.number }} - HEAD_SHA: ${{ github.event.pull_request.head.sha }} - run: | - jq -n \ - --argjson pr "$PR_NUMBER" \ - --arg sha "$HEAD_SHA" \ - '{pr_number: $pr, head_sha: $sha}' > pr-meta.json - - - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 - with: - name: release-assets - path: release-assets/ - retention-days: 14 - if-no-files-found: error - - - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 - with: - name: pr-meta - path: pr-meta.json - retention-days: 14 - if-no-files-found: error