Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
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
23 changes: 23 additions & 0 deletions .github/release-profile.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
{
"productionBranch": "master",
"developmentBranch": "develop",
"pluginSlug": "user-menus",
"pluginName": "User Menus",
"mainFile": "user-menus.php",
"versionTagPrefix": "",
"versionSources": ["package.json", "user-menus.php", "readme.txt"],
"changelogFiles": ["readme.txt"],
"artifact": {
"root": "user-menus",
"requiredPaths": ["user-menus.php", "freemius/start.php"],
"forbiddenPrefixes": [".git/", ".github/", ".wordpress-org/", "assets/js/src/", "assets/sass/", "bin/", "build/", "node_modules/", "release/", "tests/", "vendor/"]
},
"channels": {
"github": true,
"wordpressOrg": true,
"edd": false,
"googleDrive": false,
"changelogDraft": false,
"slack": false
}
}
154 changes: 131 additions & 23 deletions .github/workflows/deploy-readme-assets.yml
Original file line number Diff line number Diff line change
@@ -1,36 +1,144 @@
name: Plugin asset/readme update
name: Sync approved readme/assets PR to WordPress.org

on:
push:
branches:
- master
pull_request_target:
branches: [master]
types: [closed]
repository_dispatch:
types: [retry_approved_readme_assets_pr]

permissions:
contents: read
pull-requests: read

concurrency:
group: user-menus-publication
cancel-in-progress: false

jobs:
master:
name: Push to master
authorize:
name: Authorize approved readme/assets PR
if: >-
github.event_name == 'repository_dispatch' ||
(github.event.pull_request.merged == true &&
startsWith(github.event.pull_request.head.ref, 'wordpress-org/'))
runs-on: ubuntu-latest
outputs:
should_sync: ${{ steps.authorize.outputs.should_sync }}
merge_sha: ${{ steps.authorize.outputs.merge_sha }}
steps:
- name: Checkout master
uses: actions/checkout@master

- name: Setup PHP to use 7.4 with composer v2
uses: shivammathur/setup-php@v2
- name: Verify merged PR, approval, branch, and exact file scope
id: authorize
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
env:
RETRY_PULL_REQUEST_NUMBER: ${{ github.event.client_payload.pull_request_number }}
with:
php-version: '7.4'
tools: composer:v2
script: |
const owner = context.repo.owner;
const repo = context.repo.repo;
const number = context.eventName === 'repository_dispatch'
? Number(process.env.RETRY_PULL_REQUEST_NUMBER)
: context.payload.pull_request.number;
if (!Number.isSafeInteger(number) || number < 1) {
return core.setFailed('A valid merged readme/assets PR number is required.');
}
const { data: pull } = await github.rest.pulls.get({ owner, repo, pull_number: number });
core.setOutput('should_sync', 'false');
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}`) {
return core.setFailed('Publication PRs must come from this repository.');
}
if (!/^wordpress-org\/[a-z0-9][a-z0-9._-]*$/.test(pull.head.ref)) {
return core.setFailed('Branch must be wordpress-org/<topic> using lowercase letters, numbers, dots, underscores, or hyphens.');
}
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 candidates = [...latestByReviewer.entries()]
.filter(([, state]) => state === 'APPROVED')
.map(([login]) => login);
if (pull.merged_by?.login) candidates.push(pull.merged_by.login);
let authorized = false;
for (const username of [...new Set(candidates)]) {
const { data: permission } = await github.rest.repos.getCollaboratorPermissionLevel({ owner, repo, username });
if (['admin', 'maintain', 'write'].includes(permission.permission)) {
authorized = true;
break;
}
}
if (!authorized) {
return core.setFailed('The PR was not approved or merged by an authorized maintainer.');
}
const files = await github.paginate(github.rest.pulls.listFiles, {
owner, repo, pull_number: number, per_page: 100,
});
const allowedPath = (filename) =>
filename === 'readme.txt' || filename.startsWith('.wordpress-org/');
const removesReadme = files.some((file) =>
(file.filename === 'readme.txt' && file.status === 'removed') ||
(file.status === 'renamed' && file.previous_filename === 'readme.txt')
);
const allowed = files.length > 0 && !removesReadme && files.every((file) =>
allowedPath(file.filename) &&
(file.status !== 'renamed' || allowedPath(file.previous_filename || ''))
);
if (!allowed) {
return core.setFailed('WordPress.org publication PRs may only change readme.txt and .wordpress-org assets.');
}
core.setOutput('merge_sha', pull.merge_commit_sha);
core.setOutput('should_sync', 'true');

- name: Setup NPM Version
uses: actions/setup-node@v3
update:
name: Update WordPress.org readme and assets
needs: authorize
if: needs.authorize.outputs.should_sync == 'true'
runs-on: ubuntu-latest
steps:
- name: Checkout approved merge
uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6
with:
node-version: 10
ref: ${{ needs.authorize.outputs.merge_sha }}
fetch-depth: 0
persist-credentials: false

- name: Install Dependencies
run: npm install && composer install --prefer-dist --no-progress --no-dev
- name: Refuse stale readme or assets
run: |
git fetch --no-tags origin master
if ! git diff --quiet HEAD FETCH_HEAD -- readme.txt .wordpress-org; then
echo 'A newer readme or asset change exists on master. Refusing to publish stale files.'
exit 1
fi

- name: Build Assets
run: npm run build
- name: Validate publication files
run: |
node <<'NODE'
const fs = require('fs');
const version = JSON.parse(fs.readFileSync('package.json', 'utf8')).version;
const stableTag = fs.readFileSync('readme.txt', 'utf8')
.match(/^Stable tag:\s*([^\s]+)\s*$/m);
if (!stableTag || stableTag[1] !== version) throw new Error('Stable tag must match package.json.');
NODE
if [ -L readme.txt ] ||
[ -L .wordpress-org ] ||
{ [ -d .wordpress-org ] && find .wordpress-org -type l -print -quit | grep -q .; }; then
echo 'Symlinks are not allowed in publication files.'
exit 1
fi

- name: WordPress.org plugin asset/readme update
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 }}
SLUG: ${{ secrets.SLUG }}
SLUG: user-menus
ASSETS_DIR: .wordpress-org
IGNORE_OTHER_FILES: true
28 changes: 6 additions & 22 deletions .github/workflows/phpunit-tests.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
name: CI - PHPUnit
name: CI - PHP

on:
push:
Expand All @@ -10,36 +10,20 @@ jobs:
build:

runs-on: ubuntu-latest
name: PHPUnit tests on PHP ${{ matrix.php-version }}
name: PHP syntax on PHP ${{ matrix.php-version }}
strategy:
matrix:
php-version: [ '7.1', '7.2', '7.3', '7.4' ]

container:
image: junaidbhura/wp-tests:php-${{ matrix.php-version }}

services:
mysql:
image: mysql:5.7
env:
MYSQL_ROOT_PASSWORD: password
options: --health-cmd="mysqladmin ping" --health-interval=10s --health-timeout=5s --health-retries=3

steps:
- name: Checkout
uses: actions/checkout@v2

- name: Remove composer.lock
run: rm composer.lock
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2

- name: Validate composer.json
run: composer validate

- name: Install dependencies
run: composer install --prefer-dist --no-progress

- name: Install WordPress
run: bash bin/install-wp-tests.sh wordpress_test root 'password' mysql
run: composer validate --no-check-publish

- name: Run test suite
run: composer tests
- name: Check PHP syntax
run: find . -type f -name '*.php' -not -path './node_modules/*' -not -path './release/*' -print0 | xargs -0 -n1 php -l
71 changes: 71 additions & 0 deletions .github/workflows/pr-target-check.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
name: PR target check

on:
pull_request_target:
types: [opened, reopened, edited, synchronize]
branches: [master]

permissions:
contents: read
pull-requests: write

jobs:
redirect:
runs-on: ubuntu-latest
steps:
- name: Check for an authorized WordPress.org PR
id: target
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
with:
result-encoding: string
script: |
const pull = context.payload.pull_request;
if (pull.head.repo?.full_name !== `${context.repo.owner}/${context.repo.repo}`) {
return 'retarget';
}
if (/^release\/[0-9]+\.[0-9]+\.[0-9]+$/.test(pull.head.ref)) {
return 'allow';
}
if (!/^wordpress-org\/[a-z0-9][a-z0-9._-]*$/.test(pull.head.ref)) {
return 'retarget';
}
let permission = '';
try {
const response = await github.rest.repos.getCollaboratorPermissionLevel({
...context.repo,
username: pull.user.login,
});
permission = response.data.permission;
} catch (error) {
if (error.status !== 404) throw error;
}
if (!['admin', 'maintain', 'write'].includes(permission)) {
return 'retarget';
}
const files = await github.paginate(github.rest.pulls.listFiles, {
...context.repo,
pull_number: pull.number,
per_page: 100,
});
const allowedPath = (filename) =>
filename === 'readme.txt' || filename.startsWith('.wordpress-org/');
const removesReadme = files.some((file) =>
(file.filename === 'readme.txt' && file.status === 'removed') ||
(file.status === 'renamed' && file.previous_filename === 'readme.txt')
);
const allowed = files.length > 0 && !removesReadme && files.every((file) =>
allowedPath(file.filename) &&
(file.status !== 'renamed' || allowedPath(file.previous_filename || ''))
);
return allowed ? 'allow' : 'retarget';

- name: Retarget to develop
if: steps.target.outputs.result == 'retarget'
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
PR_NUMBER: ${{ github.event.pull_request.number }}
REPO: ${{ github.repository }}
run: |
gh pr edit "${PR_NUMBER}" --repo "${REPO}" --base develop
gh pr comment "${PR_NUMBER}" --repo "${REPO}" \
--body "Heads up — this PR was retargeted to \`develop\`. Master only accepts full releases from \`release/X.Y.Z\` or readme/assets-only updates from \`wordpress-org/<topic>\`."
Loading
Loading