├── .gitignore ├── .github └── FUNDING.yml ├── action.yml ├── package.json ├── LICENSE ├── index.js └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | # Rest pulled from https://github.com/github/gitignore/blob/master/Node.gitignore 2 | # Logs 3 | logs 4 | *.log 5 | npm-debug.log* 6 | yarn-debug.log* 7 | yarn-error.log* 8 | lerna-debug.log* 9 | # Dependency directories 10 | node_modules 11 | 12 | # Diagnostic reports (https://nodejs.org/api/report.html) 13 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 14 | 15 | .* 16 | !.gitignore 17 | !.github 18 | _* 19 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: coreybutler 4 | patreon: coreybutler 5 | open_collective: # Replace with a single Open Collective username 6 | ko_fi: # Replace with a single Ko-fi username 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | liberapay: # Replace with a single Liberapay username 10 | issuehunt: # Replace with a single IssueHunt username 11 | otechie: # Replace with a single Otechie username 12 | custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] 13 | -------------------------------------------------------------------------------- /action.yml: -------------------------------------------------------------------------------- 1 | name: "Rollback Release" 2 | description: "Rollback a git release/tag. Useful for cleaning failed build/publish processes." 3 | author: "Author.io" 4 | branding: 5 | icon: "tag" 6 | color: "red" 7 | inputs: 8 | release_id: 9 | description: The Github release ID. 10 | required: false 11 | tag: 12 | description: The tag associated with the release. 13 | required: false 14 | delete_orphan_tag: 15 | description: If the release does not exist but the tag does, setting this to true will remove the tag. 16 | required: false 17 | outputs: 18 | release_id: 19 | description: Returns the ID of the release. 20 | tag: 21 | description: The tag associated with the release. 22 | runs: 23 | using: "node20" 24 | main: "dist/index.js" 25 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "autotag-action", 3 | "version": "1.0.4", 4 | "private": true, 5 | "description": "Automatically create a tag whenever the version changes in package.json", 6 | "main": "index.js", 7 | "devDependencies": { 8 | "esbuild": "^0.15.10", 9 | "rimraf": "^3.0.2" 10 | }, 11 | "scripts": { 12 | "test": "echo \"Error: no test specified\" && exit 1", 13 | "prepare": "rimraf dist && esbuild --bundle ./index.js --format=cjs --platform=node --outdir=dist" 14 | }, 15 | "repository": { 16 | "type": "git", 17 | "url": "git+https://github.com/butlerlogic/action-autotag.git" 18 | }, 19 | "keywords": [ 20 | "actions", 21 | "node", 22 | "setup" 23 | ], 24 | "author": "ButlerLogic", 25 | "license": "MIT", 26 | "dependencies": { 27 | "@actions/core": "^1.10.0", 28 | "@actions/github": "^2.1.0" 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | The MIT License (MIT) 3 | 4 | Copyright (c) 2019 Corey Butler and contributors 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in 14 | all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | THE SOFTWARE. -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | const actionscore = require('@actions/core') 2 | const { setFailed, warning, notice, debug, setOutput } = actionscore 3 | const actionsgithub = require('@actions/github') 4 | const { GitHub, context } = actionsgithub 5 | 6 | async function run () { 7 | try { 8 | // Get authenticated GitHub client (Ocktokit): https://github.com/actions/toolkit/tree/master/packages/github#usage 9 | const github = new GitHub(process.env.GITHUB_TOKEN) 10 | 11 | // Get owner and repo from context of payload that triggered the action 12 | const { owner, repo } = context.repo 13 | 14 | // Get the inputs from the workflow file: https://github.com/actions/toolkit/tree/master/packages/core#inputsoutputs 15 | const id = process.env.RELEASE_ID || process.env.INPUT_RELEASE_ID || '' // getInput('release_id', { required: false }) 16 | const tag = process.env.TAG || process.env.INPUT_TAG || '' // getInput('tag', { required: false }) 17 | const deleteOrphan = (process.env.INPUT_DELETE_ORPHAN_TAG || '').trim().toLowerCase() === 'true' 18 | 19 | if (!id && !tag) { 20 | setFailed('At least one of the following inputs must be defined: release_id or tag.') 21 | return 22 | } 23 | 24 | // Retrieve the release ID 25 | let data 26 | if (!id) { 27 | try { 28 | data = await github.repos.getReleaseByTag({ 29 | owner, 30 | repo, 31 | tag 32 | }) 33 | } catch (e) { 34 | warning(`Could not retrieve release for ${tag}: ${e.message}`) 35 | } 36 | 37 | if (!data) { 38 | if (deleteOrphan) { 39 | const deleteTagResponse = await github.git.deleteRef({ 40 | owner, 41 | repo, 42 | ref: `tags/${tag}` 43 | }) 44 | 45 | if (deleteTagResponse) { 46 | notice(`Removed ${tag}, even though there was no associated release.`) 47 | return 48 | } 49 | } 50 | 51 | setFailed(`Tag "${tag}" was not found or a release ID is not associated with it.`) 52 | return 53 | } 54 | 55 | data = data.data 56 | } else { 57 | data = await github.repos.getRelease({ 58 | owner, 59 | repo, 60 | release_id: id 61 | }) 62 | 63 | if (!data) { 64 | debug(JSON.stringify(data, null, 2)) 65 | setFailed(`Release "${id}" was not found.`) 66 | return 67 | } 68 | 69 | data = data.data 70 | } 71 | 72 | debug(JSON.stringify(data, null, 2)) 73 | 74 | // API Documentation: https://developer.github.com/v3/repos/releases/#delete-a-release 75 | // Octokit Documentation: https://octokit.github.io/rest.js/#octokit-routes-repos-delete-release 76 | debug(`Removing release ${data.id}`) 77 | const response = await github.repos.deleteRelease({ 78 | owner, 79 | repo, 80 | release_id: data.id 81 | }) 82 | 83 | debug(JSON.stringify(response, null, 2)) 84 | 85 | // Delete tag reference 86 | debug(`Removing reference: tags/${data.tag_name}`) 87 | const tagresponse = await github.git.deleteRef({ 88 | owner, 89 | repo, 90 | ref: `tags/${data.tag_name}` 91 | }) 92 | 93 | debug(JSON.stringify(tagresponse, null, 2)) 94 | setOutput('release_id', data.id.toString()) 95 | setOutput('tag', data.tag_name.toString()) 96 | } catch (e) { 97 | warning(e.stack) 98 | setFailed(e.message) 99 | } 100 | } 101 | 102 | run() 103 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # author/action-rollback 2 | 3 | This action will rollback/delete a Github release. It is designed as a failsafe for workflows that do not complete, produce errors, fail to publish, or any other circumstance where removing a release is applicable. 4 | 5 | For example, consider the lifecycle of a Javascript package being published to npm. 6 | 7 | `test-->build-->tag-->release-->publish` 8 | 9 | In the scenario where publishing fails, it may be desirable to rollback the release. 10 | 11 | ## Workflow 12 | 13 | The following is an example `.github/publish.yml` that will rollback a release when a publish fails. 14 | 15 | Configuring the action is straightforward: 16 | 17 | ```yaml 18 | - name: Rollback Release 19 | if: failure() 20 | uses: author/action-rollback@stable 21 | with: 22 | # Using a known release ID 23 | release_id: ${{ steps.create_release.id }} 24 | # Using a tag name 25 | tag: 'v1.0.1' 26 | # If the release does not exist but the tag does, setting this to true will remove the tag. 27 | delete_orphan_tag: true 28 | env: 29 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 30 | ``` 31 | 32 | It's a bit easier to understand in context of a complete workflow: 33 | 34 | ```yaml 35 | name: Publish 36 | 37 | on: 38 | push: 39 | branches: 40 | - master 41 | 42 | permissions: # You may need this permission for removing old releases 43 | contents: write 44 | 45 | jobs: 46 | publish: 47 | runs-on: ubuntu-latest 48 | steps: 49 | - name: Tag 50 | id: autotagger 51 | uses: butlerlogic/action-autotag@stable 52 | with: 53 | GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}" 54 | 55 | - name: Release 56 | id: create_release 57 | if: steps.autotagger.outputs.tagname != '' 58 | uses: actions/create-release@v1.0.0 59 | env: 60 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 61 | with: 62 | tag_name: ${{ steps.autotagger.outputs.tagname }} 63 | release_name: Version ${{ steps.autotagger.outputs.version }} 64 | body: ${{ steps.autotagger.outputs.tagmessage }} 65 | draft: false 66 | prerelease: true 67 | 68 | - name: Publish 69 | id: publish_npm 70 | if: steps.autotagger.outputs.tagname != '' 71 | uses: author/action-publish@stable 72 | env: 73 | REGISTRY_TOKEN: ${{ secrets.REGISTRY_TOKEN }} 74 | 75 | - name: Rollback Release 76 | if: failure() && steps.create_release.outputs.id != '' 77 | uses: author/action-rollback@stable 78 | with: 79 | # Using a known release ID 80 | id: ${{ steps.create_release.id }} 81 | # Using a tag name 82 | tag: ${{ steps.autotagger.outputs.tagname }} 83 | env: 84 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 85 | ``` 86 | 87 | Only the `id` _**or**_ `tag` need to be specified. If a publish fails, the release will be removed. 88 | 89 | ### What is the purpose of `delete_orphan_tag`? 90 | 91 | It's a way to clean up messy processes. 92 | 93 | It may seem unnecessary at first. However; it is useful when a release is removed through other means, leaving an orphan tag. 94 | 95 | Technically, this attribute could be used if you only need to rollback tags of any kind (regardless of whether a release exists). 96 | 97 | --- 98 | ## Credits 99 | 100 | This action was written and is primarily maintained by [Corey Butler](https://github.com/coreybutler). 101 | 102 | # Our Ask... 103 | 104 | If you use this or find value in it, please consider contributing in one or more of the following ways: 105 | 106 | 1. Click the "Sponsor" button at the top of the page. 107 | 1. Star it! 108 | 1. [Tweet about it!](https://twitter.com/intent/tweet?hashtags=github,actions&original_referer=http%3A%2F%2F127.0.0.1%3A91%2F&text=I%20am%20automating%20my%20workflow%20with%20the%20Multipublisher%20Github%20action!&tw_p=tweetbutton&url=https%3A%2F%2Fgithub.com%2Fauthor%2Faction%2Fpublish&via=goldglovecb) 109 | 1. Fix an issue. 110 | 1. Add a feature (post a proposal in an issue first!). 111 | 112 | Copyright © 2020 Author.io, Corey Butler, and Contributors. 113 | --------------------------------------------------------------------------------