From 5304545801be010fb91145b31051c70171b0ec11 Mon Sep 17 00:00:00 2001 From: Brian Date: Fri, 13 Mar 2026 23:39:29 +0100 Subject: [PATCH 1/7] Create build-plugin-zip.yml --- .github/workflows/build-plugin-zip.yml | 64 ++++++++++++++++++++++++++ 1 file changed, 64 insertions(+) create mode 100644 .github/workflows/build-plugin-zip.yml diff --git a/.github/workflows/build-plugin-zip.yml b/.github/workflows/build-plugin-zip.yml new file mode 100644 index 00000000..4857eea6 --- /dev/null +++ b/.github/workflows/build-plugin-zip.yml @@ -0,0 +1,64 @@ +name: Build Plugin Zip + +on: + pull_request: + types: [ 'opened', 'synchronize', 'reopened', 'edited' ] + +# Cancels all previous workflow runs for pull requests that have not completed. +concurrency: + # The concurrency group contains the workflow name and the branch name for pull requests + # or the commit hash for any other events. + group: ${{ github.workflow }}-${{ github.event_name == 'pull_request' && github.head_ref || github.sha }} + cancel-in-progress: true + +# Disable permissions for all available scopes by default. +# Any needed permissions should be configured at the job level. +permissions: {} + +jobs: + build: + runs-on: 'ubuntu-24.04' + permissions: + contents: read + outputs: + job_status: ${{ job.status }} + + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + persist-credentials: false + + - name: Set up PHP and Composer + uses: shivammathur/setup-php@v2 + with: + php-version: '7.4' + coverage: none + tools: composer:v2 + + - name: Setup Node + uses: actions/setup-node@v4 + with: + node-version-file: '.nvmrc' + cache: 'npm' + + - name: Install NPM dependencies + run: npm ci + + - name: Install Composer dependencies + run: composer install --no-dev --no-interaction + + - name: Build plugin + run: npm run build + + - name: Create artifact + run: | + mv dist two-factor + zip -r two-factor.zip two-factor/ + + - name: Upload artifact + uses: actions/upload-artifact@v4 + with: + name: two-factor-plugin-zip-pr${{ github.event.pull_request.number }}-${{ github.event.pull_request.head.sha }} + path: two-factor.zip + if-no-files-found: error From 3443626aa4296cf2ce88062895c66e455fec9b5c Mon Sep 17 00:00:00 2001 From: Brian Date: Fri, 13 Mar 2026 23:42:01 +0100 Subject: [PATCH 2/7] Create pr-playground-preview.yml --- .github/workflows/pr-playground-preview.yml | 117 ++++++++++++++++++++ 1 file changed, 117 insertions(+) create mode 100644 .github/workflows/pr-playground-preview.yml diff --git a/.github/workflows/pr-playground-preview.yml b/.github/workflows/pr-playground-preview.yml new file mode 100644 index 00000000..040863ad --- /dev/null +++ b/.github/workflows/pr-playground-preview.yml @@ -0,0 +1,117 @@ +name: PR Playground Preview + +# Use workflow_run for privileged operations +# Runs with write permissions and access to secrets +# Operates on artifacts from the unprivileged build workflow +on: + workflow_run: + workflows: ["Build Plugin Zip"] + types: + - completed + +# Disable permissions for all available scopes by default. +# Any needed permissions should be configured at the job level. +permissions: {} + +jobs: + + # Leaves a comment on a pull request with a link to test the changes in a WordPress Playground instance. + playground-details: + name: Comment on a pull request with Playground details + runs-on: ubuntu-24.04 + # Only run if the build workflow succeeded and was triggered by a pull_request + if: > + github.event.workflow_run.event == 'pull_request' && + github.event.workflow_run.conclusion == 'success' + outputs: + artifact-url: ${{ steps.expose.outputs.artifact-url }} + artifact-name: ${{ steps.expose.outputs.artifact-name }} + permissions: + contents: write + pull-requests: write + + steps: + - name: Extract PR metadata from artifact name + id: pr-metadata + uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1 + with: + script: | + const artifacts = await github.rest.actions.listWorkflowRunArtifacts({ + owner: context.repo.owner, + repo: context.repo.repo, + run_id: ${{ github.event.workflow_run.id }}, + }); + + const artifact = artifacts.data.artifacts.find(a => + a.name.startsWith("two-factor-plugin-zip-pr") + ); + + if (!artifact) { + throw new Error('Could not find plugin artifact'); + } + + // Parse: two-factor-plugin-zip-pr123-abc123def... + const match = artifact.name.match(/^two-factor-plugin-zip-pr(\d+)-(.+)$/); + if (!match) { + throw new Error(`Could not parse artifact name: ${artifact.name}`); + } + + const [, prNumber, commitSha] = match; + + core.setOutput('pr-number', prNumber); + core.setOutput('commit-sha', commitSha); + core.setOutput('artifact-name', artifact.name); + + - name: Expose built artifact + id: expose + uses: WordPress/action-wp-playground-pr-preview/.github/actions/expose-artifact-on-public-url@c8607529dac8d2bf9a1e8493865fc97cd1c3c87b # v2 + with: + artifact-name: ${{ steps.pr-metadata.outputs.artifact-name }} + artifact-filename: two-factor.zip + pr-number: ${{ steps.pr-metadata.outputs.pr-number }} + commit-sha: ${{ steps.pr-metadata.outputs.commit-sha }} + artifact-source-run-id: ${{ github.event.workflow_run.id }} + artifacts-to-keep: '2' + + - name: Generate Playground blueprint JSON + id: blueprint + run: | + node - <<'NODE' >> "$GITHUB_OUTPUT" + const url = process.env.ARTIFACT_URL; + if (!url) { + throw new Error('ARTIFACT_URL is required'); + } + + const blueprint = { + landingPage: '/wp-admin/profile.php#two-factor-options', + preferredVersions: { + php: '8.2', + wp: 'latest', + }, + steps: [ + { + step: 'installPlugin', + pluginZipFile: { + resource: 'url', + url, + }, + }, + { + step: 'login', + username: 'admin', + }, + ], + }; + + console.log(`blueprint=${JSON.stringify(blueprint)}`); + NODE + env: + ARTIFACT_URL: ${{ steps.expose.outputs.artifact-url }} + + - name: Post Playground preview button + uses: WordPress/action-wp-playground-pr-preview@c8607529dac8d2bf9a1e8493865fc97cd1c3c87b # v2 + with: + mode: append-to-description + blueprint: ${{ steps.blueprint.outputs.blueprint }} + pr-number: ${{ steps.pr-metadata.outputs.pr-number }} + github-token: ${{ secrets.GITHUB_TOKEN }} From e05ef4847a561ea5462e0240542308e0feb4b244 Mon Sep 17 00:00:00 2001 From: Brian Date: Sat, 14 Mar 2026 09:53:07 +0100 Subject: [PATCH 3/7] upgrade version due to node version warning --- .github/workflows/build-plugin-zip.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/build-plugin-zip.yml b/.github/workflows/build-plugin-zip.yml index 4857eea6..ddef0b4f 100644 --- a/.github/workflows/build-plugin-zip.yml +++ b/.github/workflows/build-plugin-zip.yml @@ -25,7 +25,7 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v6 with: persist-credentials: false @@ -37,7 +37,7 @@ jobs: tools: composer:v2 - name: Setup Node - uses: actions/setup-node@v4 + uses: actions/setup-node@v5 with: node-version-file: '.nvmrc' cache: 'npm' @@ -57,7 +57,7 @@ jobs: zip -r two-factor.zip two-factor/ - name: Upload artifact - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v6 with: name: two-factor-plugin-zip-pr${{ github.event.pull_request.number }}-${{ github.event.pull_request.head.sha }} path: two-factor.zip From e05eb1dd6d56ffd750012d75bcb3dc169ae735f3 Mon Sep 17 00:00:00 2001 From: Brian Date: Sun, 29 Mar 2026 21:09:58 +0200 Subject: [PATCH 4/7] remove build-plugin-zip.yml --- .github/workflows/build-plugin-zip.yml | 64 -------------------------- 1 file changed, 64 deletions(-) delete mode 100644 .github/workflows/build-plugin-zip.yml diff --git a/.github/workflows/build-plugin-zip.yml b/.github/workflows/build-plugin-zip.yml deleted file mode 100644 index ddef0b4f..00000000 --- a/.github/workflows/build-plugin-zip.yml +++ /dev/null @@ -1,64 +0,0 @@ -name: Build Plugin Zip - -on: - pull_request: - types: [ 'opened', 'synchronize', 'reopened', 'edited' ] - -# Cancels all previous workflow runs for pull requests that have not completed. -concurrency: - # The concurrency group contains the workflow name and the branch name for pull requests - # or the commit hash for any other events. - group: ${{ github.workflow }}-${{ github.event_name == 'pull_request' && github.head_ref || github.sha }} - cancel-in-progress: true - -# Disable permissions for all available scopes by default. -# Any needed permissions should be configured at the job level. -permissions: {} - -jobs: - build: - runs-on: 'ubuntu-24.04' - permissions: - contents: read - outputs: - job_status: ${{ job.status }} - - steps: - - name: Checkout - uses: actions/checkout@v6 - with: - persist-credentials: false - - - name: Set up PHP and Composer - uses: shivammathur/setup-php@v2 - with: - php-version: '7.4' - coverage: none - tools: composer:v2 - - - name: Setup Node - uses: actions/setup-node@v5 - with: - node-version-file: '.nvmrc' - cache: 'npm' - - - name: Install NPM dependencies - run: npm ci - - - name: Install Composer dependencies - run: composer install --no-dev --no-interaction - - - name: Build plugin - run: npm run build - - - name: Create artifact - run: | - mv dist two-factor - zip -r two-factor.zip two-factor/ - - - name: Upload artifact - uses: actions/upload-artifact@v6 - with: - name: two-factor-plugin-zip-pr${{ github.event.pull_request.number }}-${{ github.event.pull_request.head.sha }} - path: two-factor.zip - if-no-files-found: error From 33de75c0850104b782349b41a68cb4b9cb26c9d2 Mon Sep 17 00:00:00 2001 From: Brian Date: Sun, 29 Mar 2026 21:18:53 +0200 Subject: [PATCH 5/7] Update pr-playground-preview.yml --- .github/workflows/pr-playground-preview.yml | 90 ++++----------------- 1 file changed, 15 insertions(+), 75 deletions(-) diff --git a/.github/workflows/pr-playground-preview.yml b/.github/workflows/pr-playground-preview.yml index 040863ad..b0473ca9 100644 --- a/.github/workflows/pr-playground-preview.yml +++ b/.github/workflows/pr-playground-preview.yml @@ -1,99 +1,42 @@ name: PR Playground Preview -# Use workflow_run for privileged operations -# Runs with write permissions and access to secrets -# Operates on artifacts from the unprivileged build workflow on: - workflow_run: - workflows: ["Build Plugin Zip"] - types: - - completed + pull_request: + types: [ 'opened', 'synchronize', 'reopened' ] + +concurrency: + group: ${{ github.workflow }}-${{ github.head_ref }} + cancel-in-progress: true -# Disable permissions for all available scopes by default. -# Any needed permissions should be configured at the job level. permissions: {} jobs: - - # Leaves a comment on a pull request with a link to test the changes in a WordPress Playground instance. - playground-details: - name: Comment on a pull request with Playground details + playground-preview: + name: Add Playground preview to PR runs-on: ubuntu-24.04 - # Only run if the build workflow succeeded and was triggered by a pull_request - if: > - github.event.workflow_run.event == 'pull_request' && - github.event.workflow_run.conclusion == 'success' - outputs: - artifact-url: ${{ steps.expose.outputs.artifact-url }} - artifact-name: ${{ steps.expose.outputs.artifact-name }} permissions: - contents: write pull-requests: write steps: - - name: Extract PR metadata from artifact name - id: pr-metadata - uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1 - with: - script: | - const artifacts = await github.rest.actions.listWorkflowRunArtifacts({ - owner: context.repo.owner, - repo: context.repo.repo, - run_id: ${{ github.event.workflow_run.id }}, - }); - - const artifact = artifacts.data.artifacts.find(a => - a.name.startsWith("two-factor-plugin-zip-pr") - ); - - if (!artifact) { - throw new Error('Could not find plugin artifact'); - } - - // Parse: two-factor-plugin-zip-pr123-abc123def... - const match = artifact.name.match(/^two-factor-plugin-zip-pr(\d+)-(.+)$/); - if (!match) { - throw new Error(`Could not parse artifact name: ${artifact.name}`); - } - - const [, prNumber, commitSha] = match; - - core.setOutput('pr-number', prNumber); - core.setOutput('commit-sha', commitSha); - core.setOutput('artifact-name', artifact.name); - - - name: Expose built artifact - id: expose - uses: WordPress/action-wp-playground-pr-preview/.github/actions/expose-artifact-on-public-url@c8607529dac8d2bf9a1e8493865fc97cd1c3c87b # v2 - with: - artifact-name: ${{ steps.pr-metadata.outputs.artifact-name }} - artifact-filename: two-factor.zip - pr-number: ${{ steps.pr-metadata.outputs.pr-number }} - commit-sha: ${{ steps.pr-metadata.outputs.commit-sha }} - artifact-source-run-id: ${{ github.event.workflow_run.id }} - artifacts-to-keep: '2' - - name: Generate Playground blueprint JSON id: blueprint run: | node - <<'NODE' >> "$GITHUB_OUTPUT" - const url = process.env.ARTIFACT_URL; - if (!url) { - throw new Error('ARTIFACT_URL is required'); - } - const blueprint = { + "$schema": "https://playground.wordpress.net/blueprint-schema.json", landingPage: '/wp-admin/profile.php#two-factor-options', preferredVersions: { - php: '8.2', + php: '7.4', wp: 'latest', }, steps: [ { step: 'installPlugin', pluginZipFile: { - resource: 'url', - url, + resource: 'git:directory', + url: 'https://github.com/${{ github.repository }}', + ref: '${{ github.head_ref }}', + path: '/', }, }, { @@ -102,16 +45,13 @@ jobs: }, ], }; - console.log(`blueprint=${JSON.stringify(blueprint)}`); NODE - env: - ARTIFACT_URL: ${{ steps.expose.outputs.artifact-url }} - name: Post Playground preview button uses: WordPress/action-wp-playground-pr-preview@c8607529dac8d2bf9a1e8493865fc97cd1c3c87b # v2 with: mode: append-to-description blueprint: ${{ steps.blueprint.outputs.blueprint }} - pr-number: ${{ steps.pr-metadata.outputs.pr-number }} + pr-number: ${{ github.event.pull_request.number }} github-token: ${{ secrets.GITHUB_TOKEN }} From 9ab350a5cb246038a61831e3236426863ef693a6 Mon Sep 17 00:00:00 2001 From: Brian Date: Sun, 29 Mar 2026 21:29:55 +0200 Subject: [PATCH 6/7] fix error --- .github/workflows/pr-playground-preview.yml | 30 +++++++++++++++++---- 1 file changed, 25 insertions(+), 5 deletions(-) diff --git a/.github/workflows/pr-playground-preview.yml b/.github/workflows/pr-playground-preview.yml index b0473ca9..2321b7be 100644 --- a/.github/workflows/pr-playground-preview.yml +++ b/.github/workflows/pr-playground-preview.yml @@ -1,11 +1,13 @@ name: PR Playground Preview on: - pull_request: - types: [ 'opened', 'synchronize', 'reopened' ] + workflow_run: + workflows: ["Test"] # replace with whatever your main CI workflow is called + types: + - completed concurrency: - group: ${{ github.workflow }}-${{ github.head_ref }} + group: ${{ github.workflow }}-${{ github.event.workflow_run.head_branch }} cancel-in-progress: true permissions: {} @@ -14,10 +16,28 @@ jobs: playground-preview: name: Add Playground preview to PR runs-on: ubuntu-24.04 + if: > + github.event.workflow_run.event == 'pull_request' && + github.event.workflow_run.conclusion == 'success' permissions: pull-requests: write steps: + - name: Get PR number + id: pr + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0 + with: + script: | + const prs = await github.rest.pulls.list({ + owner: context.repo.owner, + repo: context.repo.repo, + head: `${context.payload.workflow_run.head_repository.owner.login}:${context.payload.workflow_run.head_branch}`, + state: 'open', + }); + if (!prs.data.length) throw new Error('No open PR found for this branch'); + core.setOutput('number', prs.data[0].number); + core.setOutput('ref', context.payload.workflow_run.head_branch); + - name: Generate Playground blueprint JSON id: blueprint run: | @@ -35,7 +55,7 @@ jobs: pluginZipFile: { resource: 'git:directory', url: 'https://github.com/${{ github.repository }}', - ref: '${{ github.head_ref }}', + ref: '${{ steps.pr.outputs.ref }}', path: '/', }, }, @@ -53,5 +73,5 @@ jobs: with: mode: append-to-description blueprint: ${{ steps.blueprint.outputs.blueprint }} - pr-number: ${{ github.event.pull_request.number }} + pr-number: ${{ steps.pr.outputs.number }} github-token: ${{ secrets.GITHUB_TOKEN }} From 235e985e8cd142aa47a4bd704acc0249184f44b5 Mon Sep 17 00:00:00 2001 From: Brian Date: Sun, 29 Mar 2026 21:38:35 +0200 Subject: [PATCH 7/7] upgrade v2 blueprint --- .github/workflows/pr-playground-preview.yml | 36 ++------------------- 1 file changed, 3 insertions(+), 33 deletions(-) diff --git a/.github/workflows/pr-playground-preview.yml b/.github/workflows/pr-playground-preview.yml index 2321b7be..c9dc4fac 100644 --- a/.github/workflows/pr-playground-preview.yml +++ b/.github/workflows/pr-playground-preview.yml @@ -2,7 +2,7 @@ name: PR Playground Preview on: workflow_run: - workflows: ["Test"] # replace with whatever your main CI workflow is called + workflows: ["Test"] types: - completed @@ -20,6 +20,7 @@ jobs: github.event.workflow_run.event == 'pull_request' && github.event.workflow_run.conclusion == 'success' permissions: + contents: read pull-requests: write steps: @@ -36,42 +37,11 @@ jobs: }); if (!prs.data.length) throw new Error('No open PR found for this branch'); core.setOutput('number', prs.data[0].number); - core.setOutput('ref', context.payload.workflow_run.head_branch); - - - name: Generate Playground blueprint JSON - id: blueprint - run: | - node - <<'NODE' >> "$GITHUB_OUTPUT" - const blueprint = { - "$schema": "https://playground.wordpress.net/blueprint-schema.json", - landingPage: '/wp-admin/profile.php#two-factor-options', - preferredVersions: { - php: '7.4', - wp: 'latest', - }, - steps: [ - { - step: 'installPlugin', - pluginZipFile: { - resource: 'git:directory', - url: 'https://github.com/${{ github.repository }}', - ref: '${{ steps.pr.outputs.ref }}', - path: '/', - }, - }, - { - step: 'login', - username: 'admin', - }, - ], - }; - console.log(`blueprint=${JSON.stringify(blueprint)}`); - NODE - name: Post Playground preview button uses: WordPress/action-wp-playground-pr-preview@c8607529dac8d2bf9a1e8493865fc97cd1c3c87b # v2 with: mode: append-to-description - blueprint: ${{ steps.blueprint.outputs.blueprint }} + plugin-path: . pr-number: ${{ steps.pr.outputs.number }} github-token: ${{ secrets.GITHUB_TOKEN }}