Deploy to WordPress.org #6
Workflow file for this run
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Deploy to WordPress.org | |
| on: | |
| release: | |
| types: [published] | |
| concurrency: | |
| group: wporg-deploy-${{ github.ref_name }} | |
| cancel-in-progress: false | |
| jobs: | |
| deploy: | |
| name: Deploy to WP.org SVN | |
| runs-on: ubuntu-latest | |
| env: | |
| SLUG: xml-cache | |
| SVN_URL: https://plugins.svn.wordpress.org/xml-cache | |
| TAG_NAME: ${{ github.event.release.tag_name }} | |
| SVN_WORKDIR: wporg-svn | |
| ASSETS_DIR: svn/assets | |
| steps: | |
| - name: Checkout repository | |
| uses: actions/checkout@v4 | |
| - name: Setup PHP with Composer | |
| uses: shivammathur/setup-php@v2 | |
| with: | |
| php-version: '8.2' | |
| tools: composer | |
| coverage: none | |
| - name: Install production dependencies (Composer) | |
| shell: bash | |
| run: | | |
| composer --version | |
| composer install --no-dev --prefer-dist --no-interaction --no-progress --optimize-autoloader | |
| - name: Determine version from release tag (without v prefix) | |
| shell: bash | |
| run: | | |
| VERSION="$TAG_NAME" | |
| VERSION=${VERSION#v} | |
| echo "VERSION=$VERSION" >> $GITHUB_ENV | |
| echo "Release-Version: $VERSION" | |
| - name: Check version consistency in xml-cache.php | |
| shell: bash | |
| run: | | |
| FILE_VERSION=$(awk 'BEGIN{FS=":"} /^[[:space:]]*\*[[:space:]]*Version[[:space:]]*:/ {v=$2; gsub(/^[[:space:]]+|[[:space:]]+$/, "", v); print v; exit}' xml-cache.php || true) | |
| if [ -z "$FILE_VERSION" ]; then | |
| echo "Could not find version in xml-cache.php." >&2 | |
| exit 1 | |
| fi | |
| echo "xml-cache.php Version: $FILE_VERSION" | |
| if [ "$FILE_VERSION" != "$VERSION" ]; then | |
| echo "Version mismatch: Tag $VERSION != xml-cache.php $FILE_VERSION" >&2 | |
| exit 1 | |
| fi | |
| - name: Prepare changelog (readme.txt current only; move old to changelog.txt) | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| # Ensure jq is available for parsing the release body | |
| if ! command -v jq >/dev/null 2>&1; then | |
| sudo apt-get update -y | |
| sudo apt-get install -y jq | |
| fi | |
| echo "Preparing changelog for version $VERSION" | |
| # Extract release body from event payload | |
| RELEASE_BODY=$(jq -r '.release.body // ""' "$GITHUB_EVENT_PATH") | |
| NOTES_FILE=$(mktemp) | |
| if [ -z "$RELEASE_BODY" ]; then | |
| echo "* No release notes provided." > "$NOTES_FILE" | |
| else | |
| # Prefix non-empty lines with '* ' | |
| printf '%s\n' "$RELEASE_BODY" | sed '/./ s/^/* /' > "$NOTES_FILE" | |
| fi | |
| # Capture only the top changelog entry (first version section under the heading) | |
| TOP_ENTRY=$(mktemp) | |
| awk ' | |
| BEGIN { in_ch=0; in_top=0 } | |
| /^==[[:space:]]*Changelog[[:space:]]*==[[:space:]]*$/ { in_ch=1; next } | |
| in_ch && /^=[[:space:]]/ { if (in_top) exit; in_top=1 } | |
| in_top { print } | |
| ' readme.txt > "$TOP_ENTRY" || true | |
| # On first run, migrate the entire existing changelog section to changelog.txt | |
| if [ ! -f changelog.txt ]; then | |
| OLD_CHANGELOG=$(mktemp) | |
| awk ' | |
| BEGIN { in_ch=0 } | |
| /^==[[:space:]]*Changelog[[:space:]]*==[[:space:]]*$/ { in_ch=1; next } | |
| in_ch { print } | |
| ' readme.txt > "$OLD_CHANGELOG" || true | |
| if [ -s "$OLD_CHANGELOG" ]; then | |
| cp "$OLD_CHANGELOG" changelog.txt | |
| echo >> changelog.txt | |
| fi | |
| fi | |
| # Prepend the top entry to changelog.txt if not already present (newest first) | |
| if [ -s "$TOP_ENTRY" ]; then | |
| HEADER=$(head -n 1 "$TOP_ENTRY") | |
| echo "Preparing to append previous top entry: $HEADER" | |
| if [ -f changelog.txt ]; then | |
| if ! grep -Fqx "$HEADER" changelog.txt; then | |
| TMP_CL=$(mktemp) | |
| cat "$TOP_ENTRY" > "$TMP_CL" | |
| echo >> "$TMP_CL" | |
| cat changelog.txt >> "$TMP_CL" | |
| mv "$TMP_CL" changelog.txt | |
| else | |
| echo "Top entry already present in changelog.txt, skipping append." | |
| fi | |
| else | |
| cp "$TOP_ENTRY" changelog.txt | |
| echo >> changelog.txt | |
| fi | |
| fi | |
| # Rebuild readme.txt with only the current release under == Changelog == | |
| TMP_README=$(mktemp) | |
| awk -v ver="$VERSION" -v notes="$NOTES_FILE" ' | |
| BEGIN{printed=0} | |
| { | |
| if (printed==0) { | |
| print $0 | |
| if ($0 ~ /^==[[:space:]]*Changelog[[:space:]]*==[[:space:]]*$/) { | |
| print "" | |
| print "= " ver " =" | |
| while ((getline line < notes) > 0) print line | |
| printed=1 | |
| } | |
| } | |
| } | |
| ' readme.txt > "$TMP_README" | |
| mv "$TMP_README" readme.txt | |
| # Update Stable tag: to match the release version (optional but recommended) | |
| sed -i.bak -E "s/^(Stable tag:[[:space:]]*).*/\\1$VERSION/" readme.txt && rm -f readme.txt.bak | |
| - name: SVN Checkout | |
| shell: bash | |
| run: | | |
| # Ensure Subversion is installed (guard in case previous step is missing in old tags) | |
| if ! command -v svn >/dev/null 2>&1; then | |
| echo "svn not found. Installing..." | |
| sudo apt-get update -y | |
| sudo apt-get install -y subversion | |
| fi | |
| svn --version | |
| svn co "$SVN_URL" "$SVN_WORKDIR" | |
| - name: Synchronize files to trunk/ (with exclusions) | |
| shell: bash | |
| run: | | |
| rsync -rc --delete \ | |
| --exclude='.git/' \ | |
| --exclude='.github/' \ | |
| --exclude="$SVN_WORKDIR"/ \ | |
| --exclude='svn/' \ | |
| --exclude='react/' \ | |
| --exclude='node_modules/' \ | |
| --exclude='composer.lock' \ | |
| --exclude='phpcs.xml.dist' \ | |
| --exclude='sync.js' \ | |
| --exclude='README.md' \ | |
| ./ "$SVN_WORKDIR"/trunk/ | |
| - name: Patch vendor for WP.org PHP parser compatibility (strip PHP 8.1/8.2 types) | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| FILE="$SVN_WORKDIR/trunk/vendor/symfony/config/Definition/Builder/NodeBuilder.php" | |
| if [ -f "$FILE" ]; then | |
| echo "Patching $FILE for DNF/intersection type removal" | |
| # 1) Remove DNF typed property on $parent (preserve indentation) | |
| sed -i.bak -E "s/^([[:space:]]*)protected[[:space:]]*\(NodeDefinition\&ParentNodeDefinitionInterface\)\|null[[:space:]]*\$parent[[:space:]]*=[[:space:]]*null;/\1protected \$parent = null;/" "$FILE" | |
| # 2) Relax setParent() parameter type (keep return type 'static') | |
| sed -i.bak -E "s/public function setParent\(\(NodeDefinition\&ParentNodeDefinitionInterface\)\|null[[:space:]]*\$parent\):[[:space:]]*static/public function setParent(\$parent): static/" "$FILE" | |
| # 3) Remove intersection return type from end() | |
| sed -i.bak -E "s/public function end\(\):[[:space:]]*NodeDefinition\&ParentNodeDefinitionInterface/public function end()/" "$FILE" | |
| rm -f "$FILE".bak | |
| # 4) Verify patch applied – fail early if any intersection/DNF type remains | |
| if grep -Eq "NodeDefinition\&ParentNodeDefinitionInterface" "$FILE"; then | |
| echo "Vendor patch verification failed: intersection types still present in $FILE" >&2 | |
| echo "--- Context (head) ---" >&2 | |
| head -n 60 "$FILE" >&2 || true | |
| exit 1 | |
| fi | |
| # 5) Lint the patched file to ensure valid PHP syntax for wp.org parser | |
| php -l "$FILE" || { | |
| echo "PHP lint failed after patching vendor file: $FILE" >&2 | |
| exit 1 | |
| } | |
| else | |
| echo "Symfony NodeBuilder not found at: $FILE (skipping)" | |
| fi | |
| - name: Synchronize plugin assets to $SVN_WORKDIR/assets (from repo $ASSETS_DIR) | |
| shell: bash | |
| run: | | |
| if [ -d "$ASSETS_DIR" ]; then | |
| echo "Syncing repo assets from $ASSETS_DIR → $SVN_WORKDIR/assets" | |
| mkdir -p "$SVN_WORKDIR"/assets | |
| rsync -rc --delete \ | |
| --include='*.png' --include='*.jpg' --include='*.jpeg' --include='*.gif' --include='*.svg' \ | |
| --include='*.webp' --include='*.mp4' \ | |
| --exclude='*' \ | |
| "$ASSETS_DIR"/ "$SVN_WORKDIR"/assets/ | |
| else | |
| echo "No repo assets found in $ASSETS_DIR. Skipping." | |
| fi | |
| - name: Prepare SVN changes (add/delete) | |
| shell: bash | |
| working-directory: ${{ env.SVN_WORKDIR }} | |
| run: | | |
| svn status | |
| # Remove deleted files from SVN | |
| svn status | awk '/^!/ {print $2}' | xargs -r svn rm --force | |
| # Add new/unversioned files | |
| svn add . --force --no-auto-props | |
| svn status | |
| - name: Check if tag already exists | |
| shell: bash | |
| working-directory: ${{ env.SVN_WORKDIR }} | |
| run: | | |
| if svn ls "$SVN_URL/tags/$VERSION" >/dev/null 2>&1; then | |
| echo "Tag $VERSION already exists in WP.org SVN." >&2 | |
| exit 1 | |
| fi | |
| - name: Create tag in SVN | |
| shell: bash | |
| working-directory: ${{ env.SVN_WORKDIR }} | |
| run: | | |
| svn copy trunk "tags/$VERSION" | |
| svn status | |
| - name: Commit to WordPress.org SVN | |
| shell: bash | |
| working-directory: ${{ env.SVN_WORKDIR }} | |
| env: | |
| SVN_USERNAME: ${{ secrets.WPORG_USERNAME }} | |
| SVN_PASSWORD: ${{ secrets.WPORG_PASSWORD }} | |
| run: | | |
| if [ -z "$SVN_USERNAME" ] || [ -z "$SVN_PASSWORD" ]; then | |
| echo "Please set WPORG credentials as secrets (WPORG_USERNAME, WPORG_PASSWORD)." >&2 | |
| exit 1 | |
| fi | |
| svn commit \ | |
| --username "$SVN_USERNAME" \ | |
| --password "$SVN_PASSWORD" \ | |
| --no-auth-cache \ | |
| --non-interactive \ | |
| -m "Release $VERSION from GitHub release $TAG_NAME" | |
| - name: Output – Success Info | |
| if: success() | |
| shell: bash | |
| run: | | |
| echo "Successfully deployed: $SVN_URL/tags/$VERSION" |