├── .github ├── dependabot.yml └── workflows │ ├── assign.yml │ ├── automerge.yml │ └── release.yml ├── Dockerfile ├── LICENSE ├── Makefile ├── README.md ├── action.yml ├── entrypoint.sh ├── mock.sh ├── permissions.png └── test.bats /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: "github-actions" 4 | directory: "/" 5 | schedule: 6 | interval: "daily" 7 | - package-ecosystem: "docker" 8 | directory: "/" 9 | schedule: 10 | interval: "daily" 11 | -------------------------------------------------------------------------------- /.github/workflows/assign.yml: -------------------------------------------------------------------------------- 1 | name: Issue assignment 2 | on: 3 | issues: 4 | types: [ opened ] 5 | jobs: 6 | auto-assign: 7 | runs-on: ubuntu-latest 8 | steps: 9 | - uses: pozil/auto-assign-issue@v2 10 | if: github.actor != 'dependabot[bot]' 11 | with: 12 | repo-token: ${{ secrets.GITHUB_TOKEN }} 13 | assignees: elgohr -------------------------------------------------------------------------------- /.github/workflows/automerge.yml: -------------------------------------------------------------------------------- 1 | name: Dependabot auto-merge 2 | 3 | on: 4 | pull_request_target: 5 | branches: [ main ] 6 | types: [ opened ] 7 | 8 | permissions: 9 | pull-requests: write 10 | contents: write 11 | 12 | jobs: 13 | enableAutoMerge: 14 | runs-on: ubuntu-latest 15 | if: github.event.pull_request.user.login == 'dependabot[bot]' 16 | steps: 17 | - name: Enable auto-merge for Dependabot PRs 18 | run: gh pr merge --auto --merge "$PR_URL" 19 | env: 20 | PR_URL: ${{github.event.pull_request.html_url}} 21 | GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}} -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | 2 | name: Release 3 | on: 4 | push: 5 | branches: [ main ] 6 | tags-ignore: 7 | - '*' 8 | 9 | jobs: 10 | unit-test: 11 | runs-on: ubuntu-latest 12 | permissions: 13 | contents: read 14 | steps: 15 | - uses: actions/checkout@v4 16 | - run: make test 17 | integration-test: 18 | runs-on: ubuntu-latest 19 | needs: unit-test 20 | permissions: 21 | contents: write 22 | packages: write 23 | steps: 24 | - uses: actions/checkout@v4 25 | - name: Create a Release 26 | uses: elgohr/Github-Release-Action@main 27 | env: 28 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 29 | with: 30 | title: ${{ github.sha }} 31 | - name: Create a tagged Pre-Release 32 | uses: elgohr/Github-Release-Action@main 33 | env: 34 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 35 | with: 36 | title: ${{ github.sha }} 37 | tag: ${{ github.run_id }} 38 | prerelease: true 39 | release: 40 | runs-on: ubuntu-latest 41 | needs: integration-test 42 | steps: 43 | - uses: actions/checkout@v4 44 | with: 45 | token: ${{ secrets.RELEASE_TOKEN }} # for pushing to protected branch 46 | - name: Publish new version 47 | run: | 48 | git config --global user.email "no_reply@gohr.digital" 49 | git config --global user.name "Release Bot" 50 | git tag -fa v5 -m "Update v5 tag" 51 | git push origin v5 --force 52 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ubuntu:24.04@sha256:b59d21599a2b151e23eea5f6602f4af4d7d31c4e236d22bf0b62b86d2e386b8f as testEnv 2 | RUN apt-get update && apt-get install -y coreutils bats 3 | 4 | ADD mock.sh /usr/local/mock/gh 5 | ADD mock.sh /usr/local/mock/cd 6 | 7 | ADD entrypoint.sh /entrypoint.sh 8 | ADD test.bats /test.bats 9 | 10 | RUN /test.bats 11 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Lars 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .EXPORT_ALL_VARIABLES: 2 | DOCKER_BUILDKIT=0# to prevent caching of test results 3 | 4 | test: 5 | podman build . -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Github-Release-Action 2 | 3 | [![Actions Status](https://github.com/elgohr/Github-Release-Action/workflows/Release/badge.svg)](https://github.com/elgohr/Github-Release-Action/actions) 4 | 5 | Creates a plain Github release, without attaching assets or source code. 6 | 7 | ## Usage 8 | 9 | ```yaml 10 | name: Publish Release 11 | on: 12 | push: 13 | tags: 14 | - 'v*' 15 | jobs: 16 | build: 17 | runs-on: ubuntu-latest 18 | steps: 19 | - uses: actions/checkout@v3 20 | - name: Create a Release 21 | uses: elgohr/Github-Release-Action@v5 22 | env: 23 | GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} 24 | with: 25 | title: MyReleaseMessage 26 | ``` 27 | 28 | ## Mandatory Arguments 29 | 30 | ### title 31 | `title` is a message which should appear in the release. May contain spaces. 32 | 33 | ## Optional Arguments 34 | 35 | ### workdir 36 | `workdir` can be used to specify a directory that contains the repository to be published. 37 | 38 | ```yaml 39 | with: 40 | title: MyReleaseMessage 41 | workdir: myDirectoryName 42 | ``` 43 | 44 | ### tag 45 | `tag` can be used to set the tag of the release. 46 | 47 | ```yaml 48 | with: 49 | title: MyReleaseMessage 50 | tag: MyTag 51 | ``` 52 | 53 | ### prerelease 54 | `prerelease` is used to publish a prerelease. 55 | 56 | ```yaml 57 | with: 58 | title: MyReleaseMessage 59 | prerelease: true 60 | ``` 61 | 62 | ## Notes 63 | 64 | `${{ secrets.GITHUB_TOKEN }}` can be used for publishing, if you configure the correct permissions. 65 | 66 | This can be done by giving the Github token _all_ permissions (referred to as "Read and write permission") with the setting below available in Settings > Actions > General 67 | ![Screenshot of permission setting](permissions.png) 68 | OR alternatively it can be achieved via adding 69 | 70 | ```yaml 71 | permissions: 72 | packages: write 73 | contents: write 74 | ``` 75 | 76 | to the concrete job creating the release. For more details see the [documentation on token permissions.](https://docs.github.com/en/actions/security-guides/automatic-token-authentication#modifying-the-permissions-for-the-github_token) 77 | 78 | ### GitHub Enterprise 79 | 80 | To publish your release to self-hosted GitHub Enterprise, include `GH_ENTERPRISE_TOKEN` and `GH_HOST` as environment variables. 81 | For example: 82 | 83 | ```yaml 84 | - name: Create Release 85 | if: ${{ github.event.inputs.create_release }} 86 | uses: elgohr/Github-Release-Action@v5 87 | env: 88 | GH_ENTERPRISE_TOKEN: ${{ secrets.GITHUB_TOKEN }} 89 | GH_HOST: yourgithub.company.com 90 | with: 91 | title: "New release" 92 | tag: "v1.0.1" 93 | ``` 94 | -------------------------------------------------------------------------------- /action.yml: -------------------------------------------------------------------------------- 1 | name: "Github Release" 2 | author: "Lars Gohr" 3 | branding: 4 | icon: "git-branch" 5 | color: "gray-dark" 6 | description: "Publish Github releases from an action" 7 | 8 | inputs: 9 | title: 10 | description: "The name of the release to publish" 11 | required: true 12 | workdir: 13 | description: "Directory of the repository that the should create the release" 14 | required: false 15 | tag: 16 | description: "The tag of the release to publish" 17 | required: false 18 | prerelease: 19 | description: "Publishes a prerelease" 20 | required: false 21 | 22 | runs: 23 | using: 'composite' 24 | steps: 25 | - run: $GITHUB_ACTION_PATH/entrypoint.sh 26 | shell: bash 27 | env: 28 | INPUT_TITLE: ${{ inputs.title }} 29 | INPUT_WORKDIR: ${{ inputs.workdir }} 30 | INPUT_TAG: ${{ inputs.tag }} 31 | INPUT_PRERELEASE: ${{ inputs.prerelease }} 32 | -------------------------------------------------------------------------------- /entrypoint.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | main() { 4 | if uses "${INPUT_WORKDIR}"; then 5 | cd "${INPUT_WORKDIR}" 6 | fi 7 | 8 | if ! uses "${INPUT_TAG}"; then 9 | INPUT_TAG="release-$(date +%Y%m%d%H%M%S)" 10 | fi 11 | 12 | OPTIONS="--generate-notes" 13 | if usesBoolean "${INPUT_PRERELEASE}"; then 14 | OPTIONS="${OPTIONS} --prerelease" 15 | fi 16 | 17 | gh release create $INPUT_TAG -t "${INPUT_TITLE}" $OPTIONS 18 | } 19 | 20 | uses() { 21 | [ ! -z "${1}" ] 22 | } 23 | 24 | usesBoolean() { 25 | [ ! -z "${1}" ] && [ "${1}" = "true" ] 26 | } 27 | 28 | main 29 | -------------------------------------------------------------------------------- /mock.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | binary="$0" 3 | parameters="$@" 4 | 5 | echo "${binary} ${parameters}" >> mockArgs 6 | stdin=$(cat -) 7 | echo "${binary} ${stdin}" >> mockStdin 8 | 9 | function mockShouldFail() { 10 | [ "${MOCK_RETURNS[${binary}]}" = "_${parameters}" ] 11 | } 12 | 13 | source mockReturns 14 | 15 | if [ ! -z "${MOCK_RETURNS[${binary}]}" ] || [ ! -z "${MOCK_RETURNS[${binary} $1]}" ]; then 16 | if mockShouldFail ; then 17 | exit 1 18 | fi 19 | if [ ! -z "${MOCK_RETURNS[${binary} $1]}" ]; then 20 | echo ${MOCK_RETURNS[${binary} $1]} 21 | exit 0 22 | fi 23 | echo ${MOCK_RETURNS[${binary}]} 24 | fi 25 | 26 | exit 0 -------------------------------------------------------------------------------- /permissions.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/elgohr/Github-Release-Action/362a6a61fc5f5bbc7d813ea4c271eafeac6a265a/permissions.png -------------------------------------------------------------------------------- /test.bats: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bats 2 | 3 | setup(){ 4 | export PATH="/usr/local/mock:/usr/bin" 5 | cat /dev/null >| mockArgs 6 | cat /dev/null >| mockStdin 7 | 8 | declare -A -p MOCK_RETURNS=( 9 | ['/usr/local/mock/gh']="" 10 | ['/usr/local/mock/cd']="" 11 | ) > mockReturns 12 | 13 | export INPUT_TITLE='TITLE' 14 | } 15 | 16 | teardown() { 17 | unset INPUT_WORKDIR 18 | unset INPUT_TAG 19 | unset INPUT_PRERELEASE 20 | } 21 | 22 | @test "it creates a release" { 23 | run /entrypoint.sh 24 | 25 | expectMockCalledIs "/usr/local/mock/gh release create release-$(date +%Y%m%d%H%M%S) -t TITLE --generate-notes" 26 | } 27 | 28 | @test "it creates a release with a predefined tag" { 29 | export INPUT_TAG="TAG" 30 | 31 | run /entrypoint.sh 32 | 33 | expectMockCalledIs "/usr/local/mock/gh release create ${INPUT_TAG} -t TITLE --generate-notes" 34 | } 35 | 36 | @test "it creates a release in a working directory" { 37 | export INPUT_WORKDIR="WORKDIR" 38 | export INPUT_TAG="TAG" 39 | 40 | run /entrypoint.sh 41 | 42 | expectMockCalledIs "/usr/local/mock/gh release create ${INPUT_TAG} -t TITLE --generate-notes" 43 | } 44 | 45 | @test "it creates a prerelease" { 46 | export INPUT_TAG="TAG" 47 | export INPUT_PRERELEASE="true" 48 | 49 | run /entrypoint.sh 50 | 51 | expectMockCalledIs "/usr/local/mock/gh release create ${INPUT_TAG} -t TITLE --generate-notes --prerelease" 52 | } 53 | 54 | @test "it doesn't create a prerelease on false" { 55 | export INPUT_TAG="TAG" 56 | export INPUT_PRERELEASE="false" 57 | 58 | run /entrypoint.sh 59 | 60 | expectMockCalledIs "/usr/local/mock/gh release create ${INPUT_TAG} -t TITLE --generate-notes" 61 | } 62 | 63 | expectMockCalledIs() { 64 | local expected=$(echo "${1}" | tr -d '\n') 65 | local got=$(cat mockArgs | tr -d '\n') 66 | echo "Expected: |${expected}| 67 | Got: |${got}|" 68 | [ "${got}" == "${expected}" ] 69 | } 70 | --------------------------------------------------------------------------------