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.