Auto Release Github and Auto Update from Github, WordPress Theme

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 containsreleaseword, 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.