GitHub Actions Workflow (.yml
)
WordPress Theme Updater (.php
)
Features:
- Automated version bump and tagging on release commit.
- Automatic creation of GitHub releases with zipped theme attached.
- Automatic changelog generation from commit messages.
- WordPress dashboard update notifications for the theme, with direct GitHub zip download.
- Auto-populated theme details and changelog from GitHub release notes.
GitHub Actions Workflow (.yml
)
This GitHub Actions workflow automates version bumping and release creation for a WordPress theme. When code is pushed to the main
branch, it checks the last commit message. If the commit message contains “release” word, it reads the current version from style.css
, increments it (resetting the minor version and bumping the major version if the minor hits 999), updates style.css
, commits the change, creates a new tag, zips the theme (excluding .git
and .github
), and creates a GitHub Release attaching the zip. It also outputs the commit messages since the last release as changelog notes in the release.
name: Bump version and Release on: push: branches: - main jobs: build: runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 with: fetch-depth: '0' - name: Display current style.css before update run: cat style.css # Step to read the LAST (most recent) commit message - name: Check last commit message id: check_last_commit run: | last_commit_message=$(git log -1 --pretty=format:"%s") echo "Last commit message: $last_commit_message" echo "last_commit_message=$last_commit_message" >> $GITHUB_OUTPUT - name: Get commit messages since last release id: get_commits run: | # Read the current version from style.css for last_tag reference current_version=$(grep -oP 'Version:\s*\K[0-9.]+' style.css) last_tag="v${current_version}" echo "Last tag: $last_tag" if git rev-parse "$last_tag" >/dev/null 2>&1; then commits=$(git log "$last_tag"..HEAD --pretty=format:"- %s") else commits=$(git log --pretty=format:"- %s") fi if [ -z "$commits" ]; then commits="No changes." fi echo "commit_messages<<EOF" >> $GITHUB_ENV echo "$commits" >> $GITHUB_ENV echo "EOF" >> $GITHUB_ENV - name: Bump version in style.css if: contains(steps.check_last_commit.outputs.last_commit_message, 'release') id: bump_version run: | # Read the current version from style.css current_version=$(grep -oP 'Version:\s*\K[0-9.]+' style.css) echo "Current version: $current_version" # Split the version into an array IFS='.' read -r -a version_parts <<< "$current_version" MAJOR=${version_parts[0]} MINOR=${version_parts[1]} # Define the threshold for minor version THRESHOLD=999 if [ "$MINOR" -ge "$THRESHOLD" ]; then # Reset minor to 0 and increment major MAJOR=$((MAJOR + 1)) MINOR=0 else # Increment minor MINOR=$((MINOR + 1)) fi # Construct the new version new_version="${MAJOR}.${MINOR}" # Update the version in style.css using awk awk -v new_version="$new_version" '{ if ($1 == "Version:") { $2 = new_version } print }' style.css > style.css.tmp && mv style.css.tmp style.css # Verify the update updated_version=$(grep -oP 'Version:\s*\K[0-9.]+' style.css) echo "Updated version: $updated_version" if [ "$updated_version" != "$new_version" ]; then echo "Version update failed" exit 1 fi # Output the current and new version echo "current_version=$current_version" >> $GITHUB_ENV echo "new_version=$new_version" >> $GITHUB_ENV - name: Display current style.css after update run: cat style.css - name: Commit changes if: contains(steps.check_last_commit.outputs.last_commit_message, 'release') run: | git config --global user.name "github-actions[bot]" git config --global user.email "41898282+github-actions[bot]@users.noreply.github.com" git add style.css git commit -m "Bump version to ${{ env.new_version }}" git push - name: Create new GitHub tag if: contains(steps.check_last_commit.outputs.last_commit_message, 'release') run: | git tag -a "v${{ env.new_version }}" -m "Version ${{ env.new_version }}" git push origin "v${{ env.new_version }}" - name: Create zip file of the repository if: contains(steps.check_last_commit.outputs.last_commit_message, 'release') run: | shopt -s extglob # Enable extended globbing mkdir snn-brx-child-theme mv !(.git|*.github*|snn-brx-child-theme) snn-brx-child-theme/ zip -r snn-brx-child-theme.zip snn-brx-child-theme -x "*.git*" "*.github*" shell: /usr/bin/bash --noprofile --norc -e -o pipefail {0} env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} new_version: ${{ env.new_version }} - name: Create GitHub release if: contains(steps.check_last_commit.outputs.last_commit_message, 'release') uses: softprops/action-gh-release@v1 with: files: snn-brx-child-theme.zip tag_name: "v${{ env.new_version }}" release_name: "Release v${{ env.new_version }}" body: | Version ${{ env.new_version }} release of the project. ## Changes ${{ env.commit_messages }} env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
if you dont know how to setup git token check: https://docs.github.com/en/actions/security-for-github-actions/security-guides/automatic-token-authentication
WordPress Theme Updater (.php
)
This PHP code hooks into WordPress’s update system to enable auto-updating your child theme from GitHub releases. It checks GitHub for the latest release and compares it to the installed version. If a newer version exists, it adds an update notification in the WordPress dashboard and provides the direct download link for the theme zip from the GitHub release. It also customizes the theme info (for the theme details popup) to pull release notes and metadata from GitHub.
<?php $github_username = 'sinanisler'; $github_repo_name = 'snn-brx-child-theme'; $theme_slug = get_stylesheet(); add_filter( 'pre_set_site_transient_update_themes', 'my_child_theme_check_github_update' ); function my_child_theme_check_github_update( $transient ) { global $github_username, $github_repo_name, $theme_slug; $current_theme = wp_get_theme( $theme_slug ); $current_version = $current_theme->get( 'Version' ); $github_api_url = "https://api.github.com/repos/{$github_username}/{$github_repo_name}/releases/latest"; $response = wp_remote_get( $github_api_url, array( 'timeout' => 15, 'headers' => array( 'Accept' => 'application/vnd.github.v3+json', 'User-Agent' => 'WordPress/' . get_bloginfo( 'version' ) . '; ' . get_bloginfo( 'url' ), ), ) ); if ( is_wp_error( $response ) || wp_remote_retrieve_response_code( $response ) !== 200 ) { return $transient; } $release_data = json_decode( wp_remote_retrieve_body( $response ) ); if ( ! $release_data || ! isset( $release_data->tag_name ) ) { return $transient; } $latest_version = ltrim( $release_data->tag_name, 'vV' ); if ( version_compare( $latest_version, $current_version, '>' ) ) { $download_url = null; $expected_asset_name = $theme_slug . '.zip'; if ( isset( $release_data->assets ) && is_array( $release_data->assets ) ) { foreach ( $release_data->assets as $asset ) { if ( isset( $asset->browser_download_url ) && $asset->name === $expected_asset_name ) { $download_url = $asset->browser_download_url; break; } } } if ( $download_url ) { $transient->response[ $theme_slug ] = array( 'theme' => $theme_slug, 'new_version' => $latest_version, 'url' => $release_data->html_url, 'package' => $download_url, ); } else { } } else { } return $transient; } add_filter( 'themes_api', 'my_child_theme_github_theme_info', 10, 3 ); function my_child_theme_github_theme_info( $res, $action, $args ) { global $github_username, $github_repo_name, $theme_slug; if ( $action !== 'theme_information' || $args->slug !== $theme_slug ) { return $res; } $github_api_url = "https://api.github.com/repos/{$github_username}/{$github_repo_name}/releases/latest"; $response = wp_remote_get( $github_api_url, array( 'timeout' => 15, 'headers' => array( 'Accept' => 'application/vnd.github.v3+json', 'User-Agent' => 'WordPress/' . get_bloginfo( 'version' ) . '; ' . get_bloginfo( 'url' ), ), ) ); if ( is_wp_error( $response ) || wp_remote_retrieve_response_code( $response ) !== 200 ) { return $res; } $release_data = json_decode( wp_remote_retrieve_body( $response ) ); if ( ! $release_data || ! isset( $release_data->tag_name ) ) { return $res; } $download_link = ''; $expected_asset_name = $theme_slug . '.zip'; if ( isset( $release_data->assets ) && is_array( $release_data->assets ) ) { foreach ( $release_data->assets as $asset ) { if ( isset( $asset->browser_download_url ) && $asset->name === $expected_asset_name ) { $download_link = $asset->browser_download_url; break; } } } $res = (object) array( 'name' => $args->slug, 'slug' => $args->slug, 'version' => ltrim( $release_data->tag_name, 'vV' ), 'requires' => '5.0', 'tested' => get_bloginfo('version'), 'requires_php' => '7.4', 'download_link' => $download_link, 'sections' => array( 'description' => isset( $release_data->body ) ? $release_data->body : __( 'Latest release information from GitHub.', 'snn' ), 'changelog' => isset( $release_data->body ) ? $release_data->body : __( 'See GitHub release notes for details.', 'snn' ), ), 'added' => date( 'Y-m-d', strtotime( $release_data->published_at ) ), 'last_updated' => date( 'Y-m-d', strtotime( $release_data->published_at ) ), 'homepage' => "https://github.com/{$github_username}/{$github_repo_name}", ); return $res; }
This is a child theme example but it is similar setup for parent themes as well and similar setup for plugin as well.
Copy paste to AI if you dont know what to do.