Created
March 10, 2024 13:12
-
-
Save arabold/8ba71bb341841f421ca26d15bcf94968 to your computer and use it in GitHub Desktop.
GitHub Action: Increment Version
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: 🔄 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 |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Simple, reusable GitHub Action to automatically increment an application version and tag the git repository accordingly.
Git tag syntax:
namespace
app
major
1
minor
0
patch
0
channel
alpha
metadata
20240310
Valid examples are:
This action also facilitates a branching scheme as the one below: