Skip to content

Instantly share code, notes, and snippets.

@arabold
Created March 10, 2024 13:12
Show Gist options
  • Save arabold/8ba71bb341841f421ca26d15bcf94968 to your computer and use it in GitHub Desktop.
Save arabold/8ba71bb341841f421ca26d15bcf94968 to your computer and use it in GitHub Desktop.
GitHub Action: Increment Version
name: 🔄 Increment Version
description: Increment the repository version number
inputs:
namespace:
description: "Use to create a named sub-version. This value will be prepended to tags created for this version."
required: false
channel:
description: "Denote the channel or pre-release version, i.e. alpha, beta, rc, etc."
required: false
metadata:
description: "Metadata for the current version. This value will be ignored when looking for the latest version."
required: false
part:
description: "The part of the version to increment (major, minor, patch or none)."
required: true
default: ${{ github.ref == 'refs/heads/main' && 'minor' || 'patch' }}
change-path:
description: >-
Path to check for changes. If any changes are detected in the path the 'changed'
output will true. Enter multiple paths separated by spaces.
required: false
create-tag:
description: "Create a tag for the new version"
required: false
default: "false"
outputs:
namespace:
description: "The namespace"
value: ${{ steps.versioning.outputs.namespace }}
version:
description: "The version number in Semantic Versioning format without namespace or metadata"
value: ${{ steps.versioning.outputs.version }}
version-tag:
description: "The version tag"
value: ${{ steps.versioning.outputs.version-tag }}
major:
description: "Current major number"
value: ${{ steps.versioning.outputs.major }}
minor:
description: "Current minor number"
value: ${{ steps.versioning.outputs.minor }}
patch:
description: "Current patch number"
value: ${{ steps.versioning.outputs.patch }}
channel:
description: "The channel or pre-release version"
value: ${{ steps.versioning.outputs.channel }}
metadata:
description: "metadata for the current version"
value: ${{ steps.versioning.outputs.metadata }}
changed:
description: >-
Indicates whether there was a change since the last version if
change-path was specified. If no change-path was specified
this value will always be true since the entire repo is considered.
value: ${{ steps.versioning.outputs.changed }}
runs:
using: "composite"
steps:
- name: 🔄 Get Latest Version Tag
id: versioning
uses: actions/github-script@v7
with:
script: |
const { execSync } = require('child_process');
// Define the command to list and sort tags
const listTagsCommand = "git tag --list --sort=-version:refname";
// Execute the command to get the sorted tags
const tagsOutput = execSync(listTagsCommand).toString();
// Define the regex pattern for Semantic Versioning
const tagRegEx = /^(?:([a-zA-Z0-9][a-zA-Z0-9-_.]*)\@)?v(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$/;
// Filter the tags based on the regex
let versionTags = tagsOutput.split('\n').filter(tag => tagRegEx.test(tag));
// Log the filtered tags
console.log("Version tags:");
console.log(versionTags);
const namespace = "${{ inputs.namespace }}";
const channel = "${{ inputs.channel }}";
const metadata = "${{ inputs.metadata }}";
let latestTag;
let major = 0;
let minor = 0;
let patch = 0;
// Iterate the tags until we find the latest version that matches our namespace and channel
for (let i = 0; i < versionTags.length; i++) {
const tag = versionTags[i];
const match = tag.match(tagRegEx);
const tagNamespace = match[1] ?? "";
const tagChannel = match[5] ?? "";
if (tagNamespace === namespace && tagChannel === channel) {
// Found a matching tag
latestTag = tag;
major = parseInt(match[2]);
minor = parseInt(match[3]);
patch = parseInt(match[4]);
break;
}
}
if (!latestTag) {
console.log("No version tag found.");
}
// Pick the first tag
const part = "${{ inputs.part }}";
let changed;
if (["major", "minor", "patch"].includes(part)) {
// Test if anything has changed since the last version
const changePaths = `${{ inputs.change-path }}`.replaceAll(/[\s\r\n]+/gm, " ").trim();
changed = true;
if (changePaths && latestTag) {
const diffCommand = `git diff --name-only "${latestTag}" "HEAD" -- ${changePaths}`;
const diffOutput = execSync(diffCommand).toString();
changed = diffOutput.trim() !== "";
if (changed) {
console.log("Source changes detected:");
console.log(diffOutput);
} else {
console.log("No source changes detected.");
}
}
// Increment the version (if needed)
if (changed) {
console.log("Incrementing version.");
if (part === "major") {
major = major + 1;
minor = 0;
patch = 0;
} else if (part === "minor") {
minor = minor + 1;
patch = 0;
} else if (part === "patch") {
patch = patch + 1;
}
}
else {
console.log("No changes detected. Skipping version increment.");
}
}
else {
console.log("Skipping version increment.");
changed = false;
}
// Build the new version string
const version = `${major}.${minor}.${patch}` + (channel ? `-${channel}` : "");
const versionTag = (namespace ? `${namespace}@` : "") + `v${version}` + (metadata ? `+${metadata}` : "");
console.log(`Namespace: ${namespace}`);
console.log(`Version: ${version}`);
console.log(`Version Tag: ${versionTag}`);
console.log(`Major: ${major}`);
console.log(`Minor: ${minor}`);
console.log(`Patch: ${patch}`);
console.log(`Channel: ${channel}`);
console.log(`Metadata: ${metadata}`);
core.setOutput("namespace", namespace);
core.setOutput("version", version);
core.setOutput("version-tag", versionTag);
core.setOutput("major", major);
core.setOutput("minor", minor);
core.setOutput("patch", patch);
core.setOutput("channel", channel);
core.setOutput("metadata", metadata);
core.setOutput("changed", changed);
return versionTag;
# Create a tag for the new version
- name: 🏷️ Creating Tag
if: ${{ inputs.create-tag == 'true' && steps.versioning.outputs.changed == 'true' }}
run: |
git config --global user.email "${{ github.actor }}@users.noreply.github.com"
git config --global user.name "${{ github.actor }}"
git tag -a ${{ steps.versioning.outputs.version-tag }} -m "Version ${{ steps.versioning.outputs.version }} on ${{ github.ref_name }}"
git push origin ${{ steps.versioning.outputs.version-tag }}
shell: bash
@long1eu
Copy link

long1eu commented Jun 3, 2025

how can you use this?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment