Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
134 changes: 120 additions & 14 deletions .github/workflows/deploy-readme-assets.yml
Original file line number Diff line number Diff line change
@@ -1,28 +1,134 @@
name: Sync readme/assets to WordPress.org
name: Sync approved readme/assets PR to WordPress.org

on:
push:
branches:
- master
paths:
- readme.txt
pull_request_target:
branches: [master]
types: [closed]
workflow_dispatch:
inputs:
pull_request_number:
description: Merged readme/assets PR number to retry.
required: true
type: number

permissions:
contents: read
pull-requests: read

concurrency:
group: wordpress-org-readme-assets-${{ github.event.pull_request.number || inputs.pull_request_number }}
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Outdated
cancel-in-progress: false

jobs:
authorize:
name: Authorize approved readme/assets PR
runs-on: ubuntu-latest
outputs:
should_sync: ${{ steps.authorize.outputs.should_sync }}
merge_sha: ${{ steps.authorize.outputs.merge_sha }}
pull_request_number: ${{ steps.authorize.outputs.pull_request_number }}

steps:
- name: Verify merged PR, approval, and exact file scope
id: authorize
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
with:
script: |
const owner = context.repo.owner;
const repo = context.repo.repo;
const number = context.eventName === 'workflow_dispatch'
? Number('${{ inputs.pull_request_number }}')
: context.payload.pull_request.number;
const { data: pull } = await github.rest.pulls.get({ owner, repo, pull_number: number });

core.setOutput('should_sync', 'false');
core.setOutput('pull_request_number', String(number));

if (!pull.merged || pull.base.ref !== 'master') {
core.info('PR is not merged into master; nothing will be synced.');
return;
}
if (pull.head.repo?.full_name !== `${owner}/${repo}`) {
core.setFailed('Publication PRs must come from this repository.');
return;
}

const reviews = await github.paginate(github.rest.pulls.listReviews, {
owner,
repo,
pull_number: number,
per_page: 100,
});
const latestByReviewer = new Map();
for (const review of reviews) {
if (
review.user?.login &&
['APPROVED', 'CHANGES_REQUESTED', 'DISMISSED'].includes(review.state)
) {
latestByReviewer.set(review.user.login, review.state);
}
}
const approvedReviewers = [...latestByReviewer.entries()]
.filter(([, state]) => state === 'APPROVED')
.map(([login]) => login);
let authorizedApproval = false;
for (const username of approvedReviewers) {
const { data: permission } = await github.rest.repos.getCollaboratorPermissionLevel({
owner,
repo,
username,
});
if (['admin', 'maintain', 'write'].includes(permission.permission)) {
authorizedApproval = true;
break;
}
}
if (!authorizedApproval && pull.merged_by?.login) {
const { data: mergerPermission } = await github.rest.repos.getCollaboratorPermissionLevel({
owner,
repo,
username: pull.merged_by.login,
});
authorizedApproval = ['admin', 'maintain', 'write'].includes(
mergerPermission.permission
);
}
if (!authorizedApproval) {
core.setFailed('The PR was not approved or merged by an authorized maintainer.');
return;
}

const files = await github.paginate(github.rest.pulls.listFiles, {
owner,
repo,
pull_number: number,
per_page: 100,
});
const allowed = files.length > 0 && files.every(({ filename }) =>
filename === 'readme.txt' || filename.startsWith('.wordpress-org/')
);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Validate the source path of renamed publication files

GitHub reports a renamed file's destination as filename and its source as previous_filename, but this scope check validates only the destination. A PR that renames a file outside the permitted paths into .wordpress-org/ therefore passes as readme/assets-only and runs the narrow publication path even though it also removes a code or configuration file; the preview's git diff --name-only classification has the same behavior for detected renames. Reject renames whose source is outside the allowed paths, or validate both names.

Useful? React with 👍 / 👎.

Comment thread
coderabbitai[bot] marked this conversation as resolved.
Outdated
if (!allowed) {
core.info('PR is not readme/assets-only; the narrow SVN sync will not run.');
return;
}

core.setOutput('merge_sha', pull.merge_commit_sha);
core.setOutput('should_sync', 'true');

update:
name: Update WordPress.org readme & assets
name: Update WordPress.org readme and assets
needs: authorize
if: needs.authorize.outputs.should_sync == 'true'
runs-on: ubuntu-latest
# Only run against the protected master branch. A manual dispatch on any
# other ref must not be able to sync unreviewed content — or a malicious
# branch copy of this workflow — using the WordPress.org SVN credentials.
if: github.ref == 'refs/heads/master'

steps:
- uses: actions/checkout@v6
- name: Checkout approved merge
uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6
with:
ref: master
ref: ${{ needs.authorize.outputs.merge_sha }}
Comment on lines +128 to +131

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Refuse stale readme and asset retries

A manual retry always checks out the selected PR's historical merge SHA without verifying that its readme.txt and .wordpress-org/ contents still match master. Retrying an older approved PR after a newer readme, asset, or release update therefore sends stale files to WordPress.org and can revert the public stable tag or assets. Compare the publication paths against current master before syncing, or deploy those paths from the current branch tip.

Useful? React with 👍 / 👎.

Comment thread
coderabbitai[bot] marked this conversation as resolved.

- uses: 10up/action-wordpress-plugin-asset-update@stable
- name: Sync readme and assets
uses: 10up/action-wordpress-plugin-asset-update@2480306f6f693672726d08b5917ea114cb2825f7 # stable
env:
SVN_PASSWORD: ${{ secrets.SVN_PASSWORD }}
SVN_USERNAME: ${{ secrets.SVN_USERNAME }}
Expand Down
215 changes: 0 additions & 215 deletions .github/workflows/deploy-to-wordpress.yml

This file was deleted.

Loading
Loading