diff --git a/.github/workflows/lint.yaml b/.github/workflows/lint.yaml new file mode 100644 index 0000000..f9d7449 --- /dev/null +++ b/.github/workflows/lint.yaml @@ -0,0 +1,36 @@ +name: Lint +on: + pull_request: + branches: + - main + +permissions: + pull-requests: write + checks: write + contents: read + +jobs: + lint: + name: Run linters + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - name: Run ShellCheck + uses: reviewdog/action-shellcheck@v1 + with: + reporter: github-pr-review + level: warning + path: . + pattern: '*.sh' + fail_on_error: true + - name: Run hadolint (Haskell Dockerfile Linter) + uses: reviewdog/action-hadolint@v1 + with: + reporter: github-pr-review + level: warning + fail_on_error: true + hadolint_ignore: DL3016 DL3018 # Ignore pinning apk and npm packages to specific version with @ + - name: Run actionlint + uses: reviewdog/action-actionlint@v1 + with: + reporter: github-pr-review diff --git a/.github/workflows/main.yaml b/.github/workflows/main.yaml new file mode 100644 index 0000000..dcce837 --- /dev/null +++ b/.github/workflows/main.yaml @@ -0,0 +1,24 @@ +name: Version Manager +on: + workflow_dispatch: + pull_request: + types: + - closed + branches: + - master + +jobs: + bump-version: + runs-on: ubuntu-latest + permissions: + contents: write + steps: + - name: Checkout + uses: actions/checkout@v3 + - name: Bump version + uses: mpmcroy/monorepo-version-manager@master + env: + VERBOSE: true + DRY_RUN: true + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GIT_API_TAGGING: false diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml new file mode 100644 index 0000000..47456ed --- /dev/null +++ b/.github/workflows/test.yaml @@ -0,0 +1,370 @@ +name: Test +# This workflow tests the tag action and can be used on PRs to detect (some) breaking changes. +on: + workflow_dispatch: + pull_request: + branches: + - main + +permissions: + pull-requests: write + checks: write + contents: read + +jobs: + test-action: + name: Test action + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v3 + with: + ref: ${{ github.event.pull_request.head.sha }} + fetch-depth: '0' + + # Use the action to generate a tag for itself + - name: Test action main1 (with_v true) + id: test_main1 + uses: ./ + env: + DRY_RUN: true + WITH_V: true + VERBOSE: true + DEFAULT_BUMP: minor # default + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: Test action main2 (with_v false) + id: test_main2 + uses: ./ + env: + DRY_RUN: true + WITH_V: false + VERBOSE: true + DEFAULT_BUMP: major + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: Test action main3 (with_v false) + id: test_main3 + uses: ./ + env: + DRY_RUN: true + WITH_V: false + VERBOSE: true + DEFAULT_BUMP: none + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: Test action main4 (with_v true) + id: test_main4 + uses: ./ + env: + DRY_RUN: true + WITH_V: true + VERBOSE: true + DEFAULT_BUMP: none + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: Test action main5 (with_v true) + id: test_main5 + uses: ./ + env: + DRY_RUN: true + WITH_V: true + VERBOSE: true + DEFAULT_BUMP: patch + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: Test action component1 (with_v false) + id: test_component1 + uses: ./ + env: + COMPONENT: foobar + DRY_RUN: true + WITH_V: false + VERBOSE: true + DEFAULT_BUMP: major + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: Test action component2 (with_v false) + id: test_component2 + uses: ./ + env: + COMPONENT: foobar + DRY_RUN: true + WITH_V: false + VERBOSE: true + DEFAULT_BUMP: none + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: Test action component3 (with_v true) + id: test_component3 + uses: ./ + env: + COMPONENT: foobar + COMPONENT_DIR: .github + DRY_RUN: true + WITH_V: true + VERBOSE: true + DEFAULT_BUMP: none + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: Test action component4 (with_v true) + id: test_component4 + uses: ./ + env: + COMPONENT: foobar + COMPONENT_DIR: .github + DRY_RUN: true + WITH_V: true + VERBOSE: true + DEFAULT_BUMP: patch + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + # Use build_number versioning scheme + - name: Test action build_number_main1 (with_v true) + id: test_build_number_main1 + uses: ./ + env: + DRY_RUN: true + VERSIONING_SCHEME: build_number + INITIAL_VERSION: 0 + WITH_V: true + VERBOSE: true + DEFAULT_BUMP: minor # default + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: Test action build_number_main2 (with_v false) + id: test_build_number_main2 + uses: ./ + env: + DRY_RUN: true + VERSIONING_SCHEME: build_number + INITIAL_VERSION: 0 + WITH_V: false + VERBOSE: true + DEFAULT_BUMP: major + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: Test action build_number_main3 (with_v false) + id: test_build_number_main3 + uses: ./ + env: + DRY_RUN: true + WITH_V: false + VERBOSE: true + DEFAULT_BUMP: none + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: Test action build_number_main4 (with_v true) + id: test_build_number_main4 + uses: ./ + env: + DRY_RUN: true + VERSIONING_SCHEME: build_number + INITIAL_VERSION: 0 + WITH_V: true + VERBOSE: true + DEFAULT_BUMP: none + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: Test action build_number_main5 (with_v true) + id: test_build_number_main5 + uses: ./ + env: + DRY_RUN: true + VERSIONING_SCHEME: build_number + INITIAL_VERSION: 0 + WITH_V: true + VERBOSE: true + DEFAULT_BUMP: patch + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: Test action build_number_component1 (with_v false) + id: test_build_number_component1 + uses: ./ + env: + COMPONENT: foobar + DRY_RUN: true + VERSIONING_SCHEME: build_number + INITIAL_VERSION: 0 + WITH_V: false + VERBOSE: true + DEFAULT_BUMP: major + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: Test action build_number_component2 (with_v false) + id: test_build_number_component2 + uses: ./ + env: + COMPONENT: foobar + DRY_RUN: true + VERSIONING_SCHEME: build_number + INITIAL_VERSION: 0 + WITH_V: false + VERBOSE: true + DEFAULT_BUMP: none + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: Test action build_number_component3 (with_v true) + id: test_build_number_component3 + uses: ./ + env: + COMPONENT: foobar + COMPONENT_DIR: .github + DRY_RUN: true + VERSIONING_SCHEME: build_number + INITIAL_VERSION: 0 + WITH_V: true + VERBOSE: true + DEFAULT_BUMP: none + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: Test action build_number_component4 (with_v true) + id: test_build_number_component4 + uses: ./ + env: + COMPONENT: foobar + COMPONENT_DIR: .github + DRY_RUN: true + VERSIONING_SCHEME: build_number + INITIAL_VERSION: 0 + WITH_V: true + VERBOSE: true + DEFAULT_BUMP: patch + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + # Use the action to generate a tag for itself + - name: Test action pre1-release (with_v true) + id: test_pre1 + uses: ./ + env: + DRY_RUN: true + WITH_V: true + PRERELEASE: true + PRERELEASE_SUFFIX: test + VERBOSE: true + DEFAULT_BUMP: minor # default + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: Test action pre2-release (with_v false) + id: test_pre2 + uses: ./ + env: + DRY_RUN: true + WITH_V: false + PRERELEASE: true + PRERELEASE_SUFFIX: test + VERBOSE: true + DEFAULT_BUMP: major + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + # Check if the action created the expected output + - name: Check if the tag would have been created + shell: bash + run: | + set -x + MAIN1_OUTPUT_TAG=${{ steps.test_main1.outputs.old_tag }} + MAIN1_OUTPUT_NEWTAG=${{ steps.test_main1.outputs.new_tag }} + MAIN1_OUTPUT_PART=${{ steps.test_main1.outputs.part }} + PRE1_OUTPUT_TAG=${{ steps.test_pre1.outputs.old_tag }} + PRE1_OUTPUT_NEWTAG=${{ steps.test_pre1.outputs.new_tag }} + PRE1_OUTPUT_PART=${{ steps.test_pre1.outputs.part }} + + BUILD_NUMBER_MAIN1_OUTPUT_TAG=${{ steps.test_build_number_main1.outputs.old_tag }} + BUILD_NUMBER_MAIN1_OUTPUT_NEWTAG=${{ steps.test_build_number_main1.outputs.new_tag }} + BUILD_NUMBER_MAIN1_OUTPUT_PART=${{ steps.test_build_number_main1.outputs.part }} + + MAIN2_OUTPUT_TAG=${{ steps.test_main2.outputs.old_tag }} + MAIN2_OUTPUT_NEWTAG=${{ steps.test_main2.outputs.new_tag }} + MAIN2_OUTPUT_PART=${{ steps.test_main2.outputs.part }} + PRE2_OUTPUT_TAG=${{ steps.test_pre2.outputs.old_tag }} + PRE2_OUTPUT_NEWTAG=${{ steps.test_pre2.outputs.new_tag }} + PRE2_OUTPUT_PART=${{ steps.test_pre2.outputs.part }} + + MAIN3_OUTPUT_TAG=${{ steps.test_main3.outputs.old_tag }} + MAIN3_OUTPUT_NEWTAG=${{ steps.test_main3.outputs.new_tag }} + MAIN3_OUTPUT_PART=${{ steps.test_main3.outputs.part }} + + MAIN4_OUTPUT_TAG=${{ steps.test_main4.outputs.old_tag }} + MAIN4_OUTPUT_NEWTAG=${{ steps.test_main4.outputs.new_tag }} + MAIN4_OUTPUT_PART=${{ steps.test_main4.outputs.part }} + + MAIN5_OUTPUT_TAG=${{ steps.test_main5.outputs.old_tag }} + MAIN5_OUTPUT_NEWTAG=${{ steps.test_main5.outputs.new_tag }} + MAIN5_OUTPUT_PART=${{ steps.test_main5.outputs.part }} + + echo -e "> MAIN tests with_v, default bump:\n" >> $GITHUB_STEP_SUMMARY + + echo "MAIN1 with_v Tag: $MAIN1_OUTPUT_TAG" >> $GITHUB_STEP_SUMMARY + echo "MAIN1 with_v New tag: $MAIN1_OUTPUT_NEWTAG" >> $GITHUB_STEP_SUMMARY + echo "MAIN1 with_v Part: $MAIN1_OUTPUT_PART" >> $GITHUB_STEP_SUMMARY + + echo -e "> Pre-release tests with_v, default bump:\n" >> $GITHUB_STEP_SUMMARY + + echo "PRE1 with_v Tag: $PRE1_OUTPUT_TAG" >> $GITHUB_STEP_SUMMARY + echo "PRE1 with_v New tag: $PRE1_OUTPUT_NEWTAG" >> $GITHUB_STEP_SUMMARY + echo "PRE1 with_v Part: $PRE1_OUTPUT_PART" >> $GITHUB_STEP_SUMMARY + + echo -e "> BUILD_NUMBER_MAIN tests with_v, default bump:\n" >> $GITHUB_STEP_SUMMARY + + echo "BUILD_NUMBER_MAIN1 with_v Tag: $BUILD_NUMBER_MAIN1_OUTPUT_TAG" >> $GITHUB_STEP_SUMMARY + echo "BUILD_NUMBER_MAIN1 with_v New tag: $BUILD_NUMBER_MAIN1_OUTPUT_NEWTAG" >> $GITHUB_STEP_SUMMARY + echo "BUILD_NUMBER_MAIN1 with_v Part: $BUILD_NUMBER_MAIN1_OUTPUT_PART" >> $GITHUB_STEP_SUMMARY + + echo -e "> MAIN tests without_v, bump major:\n" >> $GITHUB_STEP_SUMMARY + + echo "MAIN2 without_v Tag: $MAIN2_OUTPUT_TAG" >> $GITHUB_STEP_SUMMARY + echo "MAIN2 without_v New tag: $MAIN2_OUTPUT_NEWTAG" >> $GITHUB_STEP_SUMMARY + echo "MAIN2 without_v Part: $MAIN2_OUTPUT_PART" >> $GITHUB_STEP_SUMMARY + + echo -e "> Pre-release tests without_v, bump major:\n" >> $GITHUB_STEP_SUMMARY + + echo "PRE2 without_v Tag: $PRE2_OUTPUT_TAG" >> $GITHUB_STEP_SUMMARY + echo "PRE2 without_v New tag: $PRE2_OUTPUT_NEWTAG" >> $GITHUB_STEP_SUMMARY + echo "PRE2 without_v Part: $PRE2_OUTPUT_PART" >> $GITHUB_STEP_SUMMARY + + echo -e "> MAIN tests without_v, bump none: (should be the same old tag no change regardless of what original tag contains or not v)\n" >> $GITHUB_STEP_SUMMARY + + echo "MAIN3 without_v Tag: $MAIN3_OUTPUT_TAG" >> $GITHUB_STEP_SUMMARY + echo "MAIN3 without_v New tag: $MAIN3_OUTPUT_NEWTAG" >> $GITHUB_STEP_SUMMARY + echo "MAIN3 without_v Part: $MAIN3_OUTPUT_PART" >> $GITHUB_STEP_SUMMARY + + echo -e "> MAIN tests with_v, bump none: (should be the same old tag no change regardless of what original tag contains or not v)\n" >> $GITHUB_STEP_SUMMARY + + echo "MAIN4 with_v Tag: $MAIN4_OUTPUT_TAG" >> $GITHUB_STEP_SUMMARY + echo "MAIN4 with_v New tag: $MAIN4_OUTPUT_NEWTAG" >> $GITHUB_STEP_SUMMARY + echo "MAIN4 with_v Part: $MAIN4_OUTPUT_PART" >> $GITHUB_STEP_SUMMARY + + echo -e "> MAIN tests with_v, bump patch:\n" >> $GITHUB_STEP_SUMMARY + + echo "MAIN5 with_v Tag: $MAIN5_OUTPUT_TAG" >> $GITHUB_STEP_SUMMARY + echo "MAIN5 with_v New tag: $MAIN5_OUTPUT_NEWTAG" >> $GITHUB_STEP_SUMMARY + echo "MAIN5 with_v Part: $MAIN5_OUTPUT_PART" >> $GITHUB_STEP_SUMMARY + + # check that the original tag got bumped either major, minor, patch + verlte() { + [ "$1" = "`echo -e "$1\n$2" | sort -V | head -n1`" ] + } + verlt() { + [ "$1" = "$2" ] && return 1 || verlte $1 $2 + } + + # needs to be a greater tag in default minor + main1="$(verlt $MAIN1_OUTPUT_TAG $MAIN1_OUTPUT_NEWTAG && true || false)" + pre1="$(verlt $PRE1_OUTPUT_TAG $PRE1_OUTPUT_NEWTAG && true || false)" + build_number_main1="$(verlt $BUILD_NUMBER_MAIN1_OUTPUT_TAG $BUILD_NUMBER_MAIN1_OUTPUT_NEWTAG && true || false)" + # needs to be a greater tag in bump major + main2="$(verlt $MAIN2_OUTPUT_TAG $MAIN2_OUTPUT_NEWTAG && true || false)" + pre2="$(verlt $PRE2_OUTPUT_TAG $PRE2_OUTPUT_NEWTAG && true || false)" + # needs to be the latest tag of the repo when bump is none + main3="$([ "$MAIN3_OUTPUT_TAG" = "$MAIN3_OUTPUT_NEWTAG" ] && true || false)" + main4="$([ "$MAIN4_OUTPUT_TAG" = "$MAIN4_OUTPUT_NEWTAG" ] && true || false)" + # needs to be a greater tag in bump patch + main5="$(verlt $MAIN5_OUTPUT_TAG $MAIN5_OUTPUT_NEWTAG && true || false)" + + if $main1 && $pre1 && $build_number_main1 && $main2 && $pre2 && $main3 && $main4 && $main5 + then + echo -e "\n>>>>The tags were created correctly" >> $GITHUB_STEP_SUMMARY + else + echo -e "\n>>>>Tags NOT created correctly" >> $GITHUB_STEP_SUMMARY + exit 1 + fi diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..4277e50 --- /dev/null +++ b/.gitignore @@ -0,0 +1,21 @@ +### IntelliJ IDEA ### +.idea +*.iws +*.iml +*.ipr +out/ +!**/src/main/**/out/ +!**/src/test/**/out/ + +### NetBeans ### +/nbproject/private/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ + +### VS Code ### +.vscode/ + +### Mac ### +.DS_Store diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..ca38645 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,7 @@ +FROM node:18-alpine + +RUN apk --no-cache add bash git curl jq && npm install -g semver + +COPY entrypoint.sh /entrypoint.sh + +ENTRYPOINT ["/entrypoint.sh"] diff --git a/README.md b/README.md old mode 100644 new mode 100755 index ac235e0..8b5a1c8 --- a/README.md +++ b/README.md @@ -1,2 +1,74 @@ # monorepo-version-manager -GitHub Action for managing versions (and creating corresponding Git tags) in monorepos + +GitHub Action for managing versions (and creating corresponding Git tags) in monorepos. Inspired by [anothrNick/github-tag-action](https://github.com/anothrNick/github-tag-action). + +[![Build Status](https://github.com/mpmcroy/monorepo-version-manager/workflows/Bump%20version/badge.svg)](https://github.com/mpmcroy/monorepo-version-manager/workflows/Bump%20version/badge.svg) +[![Latest Release](https://img.shields.io/github/v/release/mpmcroy/gmonorepo-version-manager?color=%233D9970)](https://img.shields.io/github/v/release/mpmcroy/monorepo-version-manager?color=%233D9970) + +## Usage +```yaml +# Bump semver version (main component) +- uses: mpmcroy/monorepo-version-manager@0.1.0 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + +# Bump semver version (foobar component) +- uses: mpmcroy/monorepo-version-manager@0.1.0 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + COMPONENT_NAME: foobar + COMPONENT_DIR: foobar + +# Bump build_number version (main component) +- uses: mpmcroy/monorepo-version-manager@0.1.0 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + COMPONENT_NAME: config + COMPONENT_DIR: config + VERSIONING_SCHEME: build_number + INITIAL_VERSION: 0 +``` + +## Options + +| Name | Required | Description | Default | +|:-------------------|:---------|:-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|:-----------------| +| GITHUB_TOKEN | required | | | +| DEFAULT_BUMP | optional | Type of bump to use when none explicitly provided | minor | +| DEFAULT_BRANCH | optional | Overwrite the default branch its read from Github Runner env var but can be overwritten (default: $GITHUB_BASE_REF). Strongly recommended to set this var if using anything else than master or main as default branch otherwise in combination with history full will error | $GITHUB_BASE_REF | +| COMPONENT_NAME | optional | Name of the component to version | main | +| COMPONENT_DIR | optional | Directory path to component | . | +| VERSIONING_SCHEME | optional | Versioning scheme to use (valid values are 'semver' and 'build_number') | semver | +| WITH_V | optional | Prefix version tag with `v` | false | +| RELEASE_BRANCHES | optional | Comma separated list of branches (bash reg exp accepted) that will generate the release tags. Other branches and pull-requests generate versions postfixed with the commit hash and do not generate any tag (e.g. `master` or `.*` or `release.*,hotfix.*,master`) | master,main | +| CUSTOM_TAG | optional | Set a custom tag, useful when generating tag based on f.ex FROM image in a docker image. **Setting this tag will invalidate any other settings set!** | | +| SOURCE | optional | Operate on a relative path under $GITHUB_WORKSPACE | . | +| DRY_RUN | optional | Determine the next version without tagging the branch | false | +| GIT_API_TAGGING | optional | Set if using git cli or git api calls for tag push operations | true | +| INITIAL_VERSION | optional | Initial version before bump. **Required when using build_number versioning scheme (e.g. 0)** | 0.0.0 | +| TAG_CONTEXT | optional | Set the context of the previous tag (valid values 'repo' and 'branch') | repo | +| PRERELEASE | optional | Define if workflow runs in prerelease mode. Note this will be overwritten if using complex suffix release branches. Use it with checkout `ref: ${{ github.sha }}` for consistency see [issue 266](https://github.com/anothrNick/github-tag-action/issues/266) | false | +| PRERELEASE_SUFFIX | optional | Suffix for your prerelease versions. Note this will only be used if a prerelease branch | beta | +| VERBOSE | optional | Enable verbose logging | false | +| MAJOR_STRING_TOKEN | optional | String in commit log to search for to bump major version | #major | +| MINOR_STRING_TOKEN | optional | String in commit log to search for to bump minor version | #minor | +| PATCH_STRING_TOKEN | optional | String in commit log to search for to bump patch version | #patch | +| NONE_STRING_TOKEN | optional | String in commit log to search for to prevent version bump | #none | +| BRANCH_HISTORY | optional | Set the history of the branch for finding '#bumps' (valid values are Possible values 'last' (single last commit), 'full' (all history, although semi-broken, do-not-use) and 'compare' (all commits since previous)) | compare | + +## Outputs + +| Name | Description | +|:--------|:------------------------------------------------------------------------------------| +| old_tag | Version tag before GitHub action run | +| new_tag | Version tag after GitHub action run | +| part | Version part that was bumped (not relevant when VERSIONING_SCHEME is 'build_number' | + +## Version Bump + +**Explicit:** Any commit message that includes `#major`, `#minor`, `#patch`, or `#none` will trigger the respective version bump. If two or more are present, the highest-ranking one will take precedence. +If `#none` is contained in the merge commit message, it will skip bumping regardless `DEFAULT_BUMP`. + +**Implicit:** If no `#major`, `#minor` or `#patch` tag is contained in the merge commit message, it will bump whichever `DEFAULT_BUMP` is set to (which is `minor` by default). Disable this by setting `DEFAULT_BUMP` to `none`. + +> **_Note:_** Version will not be bumped if there is no code difference since the last tagged version. Code difference is checked using `git diff`. diff --git a/action.yml b/action.yml new file mode 100644 index 0000000..3235304 --- /dev/null +++ b/action.yml @@ -0,0 +1,13 @@ +name: 'Monorepo Version Manager' +description: 'GitHub Action for managing versions (and creating corresponding Git tags) in monorepos' +author: 'Martin McRoy' +runs: + using: 'docker' + image: 'Dockerfile' +outputs: + old_tag: + description: 'The version tag before GitHub action run' + new_tag: + description: 'The version tag after GitHub action run' + part: + description: 'The version part that was bumped (not relevant for build_number versioning scheme)' diff --git a/entrypoint.sh b/entrypoint.sh new file mode 100755 index 0000000..333bf12 --- /dev/null +++ b/entrypoint.sh @@ -0,0 +1,338 @@ +#!/bin/bash + +set -eo pipefail + +# config +default_semvar_bump=${DEFAULT_BUMP:-minor} +default_branch=${DEFAULT_BRANCH:-$GITHUB_BASE_REF} # get the default branch from github runner env vars +component_name=${COMPONENT_NAME:-main} +component_dir=${COMPONENT_DIR:-.} +versioning_scheme=${VERSIONING_SCHEME:-semver} # valid values (semver, build_number) +with_v=${WITH_V:-false} +release_branches=${RELEASE_BRANCHES:-master,main} +custom_tag=${CUSTOM_TAG:-} +source=${SOURCE:-.} +dryrun=${DRY_RUN:-false} +git_api_tagging=${GIT_API_TAGGING:-true} +initial_version=${INITIAL_VERSION:-0.0.0} #TODO: Add initial_version for build_number versioning scheme +tag_context=${TAG_CONTEXT:-repo} +prerelease=${PRERELEASE:-false} +suffix=${PRERELEASE_SUFFIX:-beta} +verbose=${VERBOSE:-false} +major_string_token=${MAJOR_STRING_TOKEN:-#major} +minor_string_token=${MINOR_STRING_TOKEN:-#minor} +patch_string_token=${PATCH_STRING_TOKEN:-#patch} +none_string_token=${NONE_STRING_TOKEN:-#none} +branch_history=${BRANCH_HISTORY:-compare} +# since https://github.blog/2022-04-12-git-security-vulnerability-announced/ runner uses? +git config --global --add safe.directory /github/workspace + +cd "${GITHUB_WORKSPACE}/${source}" || exit 1 + +echo "*** CONFIGURATION ***" +echo -e "\tDEFAULT_BUMP: ${default_semvar_bump}" +echo -e "\tDEFAULT_BRANCH: ${default_branch}" +echo -e "\tCOMPONENT_NAME: ${component_name}" +echo -e "\tCOMPONENT_DIR: ${component_dir}" +echo -e "\tVERSIONING_SCHEME: ${versioning_scheme}" +echo -e "\tWITH_V: ${with_v}" +echo -e "\tRELEASE_BRANCHES: ${release_branches}" +echo -e "\tCUSTOM_TAG: ${custom_tag}" +echo -e "\tSOURCE: ${source}" +echo -e "\tDRY_RUN: ${dryrun}" +echo -e "\tGIT_API_TAGGING: ${git_api_tagging}" +echo -e "\tINITIAL_VERSION: ${initial_version}" +echo -e "\tTAG_CONTEXT: ${tag_context}" +echo -e "\tPRERELEASE: ${prerelease}" +echo -e "\tPRERELEASE_SUFFIX: ${suffix}" +echo -e "\tVERBOSE: ${verbose}" +echo -e "\tMAJOR_STRING_TOKEN: ${major_string_token}" +echo -e "\tMINOR_STRING_TOKEN: ${minor_string_token}" +echo -e "\tPATCH_STRING_TOKEN: ${patch_string_token}" +echo -e "\tNONE_STRING_TOKEN: ${none_string_token}" +echo -e "\tBRANCH_HISTORY: ${branch_history}" + +if [[ "${versioning_scheme}" != @(semver|build_number) ]] +then + echo "::error::Invalid versioning_scheme. Must be one of (semver, build_number)." + exit 1 +fi +if [[ "${versioning_scheme}" == build_number && ${prerelease} == "true" ]] +then + echo "::error::Pre-release not supported for versioning_scheme build_number" + exit 1 +fi + +# verbose, show everything +if $verbose +then + set -x +fi + +setOutput() { + echo "${1}=${2}" >> "${GITHUB_OUTPUT}" +} + +bumpVersion() { + local versioning_scheme="${1}" + local bump_type="${2}" + local version_tag="${3}" + local result + + case $versioning_scheme in + semver ) + result=$(semver -i "${bump_type}" "${version_tag}") + ;; + build_number ) + build_number_value=$((version_tag)) + ((build_number_value++)) + result="${build_number_value}" + ;; + * ) + echo "Unsupported versioning_scheme: ${versioning_scheme}" + exit 1 + ;; + esac + + echo "${result}" +} + +current_branch=$(git rev-parse --abbrev-ref HEAD) + +pre_release="$prerelease" +IFS=',' read -ra branch <<< "$release_branches" +for b in "${branch[@]}"; do + # check if ${current_branch} is in ${release_branches} | exact branch match + if [[ "$current_branch" == "$b" ]] + then + pre_release="false" + fi + # verify non specific branch names like .* release/* if wildcard filter then =~ + if [ "$b" != "${b//[\[\]|.? +*]/}" ] && [[ "$current_branch" =~ $b ]] + then + pre_release="false" + fi +done +echo "pre_release = $pre_release" + +# fetch tags +git fetch --tags + +semverTagFmt="^(${component_name}-)v?[0-9]+\.[0-9]+\.[0-9]+$" +semverPreTagFmt="^(${component_name}-)v?[0-9]+\.[0-9]+\.[0-9]+(-${suffix}\.[0-9]+)$" +buildNumberTagFmt="^(${component_name}-)v?[0-9]+$" +buildNumberPreTagFmt="^(${component_name}-)v?[0-9]+(-${suffix}\.[0-9]+)$" + +component_tags=$(git tag -l "${component_name}-*" --sort=-v:refname) + +case $versioning_scheme in + semver ) + matching_component_tag_refs=$( (grep -E "${semverTagFmt}" <<< "${component_tags}") || true) + matching_component_pre_tag_refs=$( (grep -E "${semverPreTagFmt}" <<< "${component_tags}") || true) + ;; + build_number ) + matching_component_tag_refs=$( (grep -E "${buildNumberTagFmt}" <<< "${component_tags}") || true) + matching_component_pre_tag_refs=$( (grep -E "${buildNumberPreTagFmt}" <<< "${component_tags}") || true) + ;; + * ) + echo "Unsupported versioning_scheme: ${versioning_scheme}" + exit 1 + ;; +esac + +component_tag=$(head -n 1 <<< "${matching_component_tag_refs}") +component_pre_tag=$(head -n 1 <<< "${matching_component_pre_tag_refs}") +tag=${component_tag#"${component_name}-"} +pre_tag=${component_pre_tag#"${component_name}-"} + +# if there are none, start tags at INITIAL_VERSION +if [ -z "$tag" ] +then + if $with_v + then + tag="v$initial_version" + else + tag="$initial_version" + fi + if [ -z "$pre_tag" ] && $pre_release + then + if $with_v + then + pre_tag="v$initial_version" + else + pre_tag="$initial_version" + fi + fi + tag_initialized=true +fi + +# get current commit hash for tag +tag_commit=$(git rev-list -n 1 "${component_name}-${tag}" || true ) +# get current commit hash +commit=$(git rev-parse HEAD) + +if ! ${tag_initialized} +then + component_diff=$(git diff "${component_tag:-HEAD}" HEAD -- "${component_dir}") + if [ -z "${component_diff}" ] + then + echo "No new commits since previous tag. Skipping..." + setOutput "old_tag" "${component_name}-${tag}" + setOutput "new_tag" "${component_name}-${tag}" + exit 0 + fi +fi + +# sanitize that the default_branch is set (via env var when running on PRs) else find it natively +if [ -z "${default_branch}" ] && [ "$branch_history" == "full" ] +then + echo "The DEFAULT_BRANCH should be autodetected when tag-action runs on on PRs else must be defined, See: https://github.com/anothrNick/github-tag-action/pull/230, since is not defined we find it natively" + default_branch=$(git branch -rl '*/master' '*/main' | cut -d / -f2) + echo "default_branch=${default_branch}" + # re check this + if [ -z "${default_branch}" ] + then + echo "::error::DEFAULT_BRANCH must not be null, something has gone wrong." + exit 1 + fi +fi + +# get the merge commit message looking for #bumps +declare -A history_type=( + ["last"]="$(git show -s --format=%B)" \ + ["full"]="$(git log "${default_branch}"..HEAD --format=%B)" \ + ["compare"]="$(git log "${tag_commit}".."${commit}" --format=%B)" \ +) +log=${history_type[${branch_history}]} +printf "History:\n---\n%s\n---\n" "$log" + +case "$log" in + *$major_string_token* ) + new=$(bumpVersion "${versioning_scheme}" major "${tag}") + part="major" + ;; + *$minor_string_token* ) + new=$(bumpVersion "${versioning_scheme}" minor "${tag}") + part="minor" + ;; + *$patch_string_token* ) + new=$(bumpVersion "${versioning_scheme}" patch "${tag}") + part="patch" + ;; + *$none_string_token* ) + echo "Default bump was set to none. Skipping..." + setOutput "old_tag" "${component_name}-${tag}" + setOutput "new_tag" "${component_name}-${tag}" + setOutput "part" "$default_semvar_bump" + exit 0;; + * ) + if [ "$default_semvar_bump" == "none" ] + then + echo "Default bump was set to none. Skipping..." + setOutput "old_tag" "${component_name}-${tag}" + setOutput "new_tag" "${component_name}-${tag}" + setOutput "part" "$default_semvar_bump" + exit 0 + else + new=$(bumpVersion "${versioning_scheme}" "${default_semvar_bump}" "${tag}") + part=$default_semvar_bump + fi + ;; +esac + +if $pre_release +then + # get current commit hash for tag + pre_tag_commit=$(git rev-list -n 1 "${component_name}-${pre-tag}" || true) + # skip if there are no new commits for pre_release + if [ "$pre_tag_commit" == "$commit" ] + then + echo "No new commits since previous pre_tag. Skipping..." + setOutput "old_tag" "${component_name}-${pre-tag}" + setOutput "new_tag" "${component_name}-${pre-tag}" + exit 0 + fi + # already a pre-release available, bump it + if [[ "$pre_tag" =~ $new ]] && [[ "$pre_tag" =~ $suffix ]] + then + if $with_v + then + new=v$(semver -i prerelease "${pre_tag}" --preid "${suffix}") + else + new=$(semver -i prerelease "${pre_tag}" --preid "${suffix}") + fi + echo -e "Bumping ${suffix} pre-tag ${pre_tag}. New pre-tag ${new}" + else + if $with_v + then + new="v$new-$suffix.0" + else + new="$new-$suffix.0" + fi + echo -e "Setting ${suffix} pre-tag ${pre_tag} - With pre-tag ${new}" + fi + part="pre-$part" +else + if $with_v + then + new="v$new" + fi + echo -e "Bumping tag ${component_name}-${tag} - New tag ${component_name}-${new}" +fi + +# as defined in readme if CUSTOM_TAG is used any semver calculations are irrelevant. +if [ -n "$custom_tag" ] +then + new="$custom_tag" +fi + +# set outputs +setOutput "old_tag" "${component_name}-${tag}" +setOutput "new_tag" "${component_name}-${new}" +setOutput "part" "${component_name}-${part}" + +# dry run exit without real changes +if $dryrun +then + exit 0 +fi + +echo "EVENT: creating local tag ${component_name}-${new}" +# create local git tag +git tag -f "${component_name}-${new}" || exit 1 +echo "EVENT: pushing tag ${component_name}-${new} to origin" + +if $git_api_tagging +then + # use git api to push + dt=$(date '+%Y-%m-%dT%H:%M:%SZ') + full_name=$GITHUB_REPOSITORY + git_refs_url=$(jq .repository.git_refs_url "$GITHUB_EVENT_PATH" | tr -d '"' | sed 's/{\/sha}//g') + + echo "$dt: **pushing tag ${component_name}-${new} to repo $full_name" + + git_refs_response=$( + curl -s -X POST "$git_refs_url" \ + -H "Authorization: token $GITHUB_TOKEN" \ + -d @- << EOF +{ + "ref": "refs/tags/${component_name}-${new}", + "sha": "$commit" +} +EOF +) + + git_ref_posted=$( echo "${git_refs_response}" | jq .ref | tr -d '"' ) + + echo "::debug::${git_refs_response}" + if [ "${git_ref_posted}" = "refs/tags/${component_name}-${new}" ] + then + exit 0 + else + echo "::error::Tag was not created properly." + exit 1 + fi +else + # use git cli to push + git push -f origin "${component_name}-${new}" || exit 1 +fi