name: Create Internal Release Tag on: workflow_dispatch: inputs: release_type: description: "Type of release (auto|patch|minor|major|hotfix)" required: true default: auto type: choice options: [auto, patch, minor, major, hotfix] hotfix_base_version: description: "Base version for hotfix (e.g. 2.5.0) required if release_type=hotfix" required: false dry_run: description: "If true, calculate but do not push tag" required: false default: "false" permissions: contents: write jobs: create-internal-tag: runs-on: ubuntu-latest steps: - name: Checkout repository uses: actions/checkout@v5 with: fetch-depth: 0 - name: Determine Latest Base Version id: latest run: | set -euo pipefail # List base tags (exclude track/hotfix suffixes) BASE_TAGS=$(git tag --list 'v[0-9]*.[0-9]*.[0-9]*' --sort=-version:refname | head -n1 || true) echo "Found latest base tag: $BASE_TAGS" echo "latest_base_tag=$BASE_TAGS" >> $GITHUB_OUTPUT - name: Compute Next Version (auto/patch/minor/major) id: compute if: ${{ inputs.release_type != 'hotfix' }} run: | set -euo pipefail RTYPE='${{ inputs.release_type }}' LAST='${{ steps.latest.outputs.latest_base_tag }}' if [ -z "$LAST" ]; then BASE_MAJOR=0; BASE_MINOR=0; BASE_PATCH=0 else V=${LAST#v} IFS='.' read -r BASE_MAJOR BASE_MINOR BASE_PATCH <<< "$V" fi if [ "$RTYPE" = 'auto' ]; then echo "Determining bump type from commits since $LAST..." RANGE="$LAST..HEAD" [ -z "$LAST" ] && RANGE="HEAD" # first release LOG=$(git log --format=%s $RANGE || true) BUMP="patch" if echo "$LOG" | grep -Eiq 'BREAKING CHANGE'; then BUMP=major; fi if echo "$LOG" | grep -Eiq '^[a-zA-Z]+!:'; then BUMP=major; fi if [ "$BUMP" != major ] && echo "$LOG" | grep -Eiq '^feat(\(|:)' ; then BUMP=minor; fi RTYPE=$BUMP echo "Auto-detected bump: $RTYPE" fi case "$RTYPE" in major) NEW_MAJOR=$((BASE_MAJOR+1)); NEW_MINOR=0; NEW_PATCH=0;; minor) NEW_MAJOR=$BASE_MAJOR; NEW_MINOR=$((BASE_MINOR+1)); NEW_PATCH=0;; patch) NEW_MAJOR=$BASE_MAJOR; NEW_MINOR=$BASE_MINOR; NEW_PATCH=$((BASE_PATCH+1));; *) echo "Unsupported release_type for this step: $RTYPE"; exit 1;; esac NEW_VERSION="${NEW_MAJOR}.${NEW_MINOR}.${NEW_PATCH}" echo "base_version=$NEW_VERSION" >> $GITHUB_OUTPUT - name: Compute Hotfix Version id: hotfix if: ${{ inputs.release_type == 'hotfix' }} run: | set -euo pipefail BASE='${{ inputs.hotfix_base_version }}' if [ -z "$BASE" ]; then echo "hotfix_base_version required for hotfix release_type" >&2 exit 1 fi if ! git tag --list | grep -q "^v$BASE$"; then echo "Base version tag v$BASE not found (production tag required)." >&2 exit 1 fi EXISTING=$(git tag --list "v${BASE}-hotfix*" | sed -E 's/^v[0-9]+\.[0-9]+\.[0-9]+-hotfix([0-9]+).*$/\1/' | sort -n | tail -1 || true) if [ -z "$EXISTING" ]; then NEXT=1; else NEXT=$((EXISTING+1)); fi HOTFIX_VERSION="${BASE}-hotfix${NEXT}" echo "hotfix_version=$HOTFIX_VERSION" >> $GITHUB_OUTPUT - name: Decide Internal Tag id: tag run: | set -euo pipefail if [ '${{ inputs.release_type }}' = 'hotfix' ]; then BASE='${{ steps.hotfix.outputs.hotfix_version }}' else BASE='${{ steps.compute.outputs.base_version }}' fi INTERNAL_TAG="v${BASE}-internal.1" if git tag --list | grep -q "^${INTERNAL_TAG}$"; then echo "Tag ${INTERNAL_TAG} already exists." >&2 exit 1 fi echo "internal_tag=$INTERNAL_TAG" >> $GITHUB_OUTPUT - name: Dry Run Preview if: ${{ inputs.dry_run == 'true' }} run: | echo "DRY RUN: Would create tag ${{ steps.tag.outputs.internal_tag }} pointing to $(git rev-parse HEAD)" git log -5 --oneline - name: Create and Push Tag if: ${{ inputs.dry_run != 'true' }} run: | TAG='${{ steps.tag.outputs.internal_tag }}' MSG="Initial internal build for ${TAG}" git tag -a "$TAG" -m "$MSG" git push origin "$TAG" echo "Created and pushed $TAG" - name: Output Summary run: | echo "### Internal Tag Created" >> $GITHUB_STEP_SUMMARY echo "Tag: ${{ steps.tag.outputs.internal_tag }}" >> $GITHUB_STEP_SUMMARY echo "Release Type: ${{ inputs.release_type }}" >> $GITHUB_STEP_SUMMARY if [ '${{ inputs.release_type }}' = 'hotfix' ]; then echo "Base Hotfix Series: ${{ steps.hotfix.outputs.hotfix_version }}" >> $GITHUB_STEP_SUMMARY else echo "Base Version: ${{ steps.compute.outputs.base_version }}" >> $GITHUB_STEP_SUMMARY fi echo "Dry Run: ${{ inputs.dry_run }}" >> $GITHUB_STEP_SUMMARY