├── screenshot.jpg ├── package.json ├── LICENSE ├── .gitignore ├── action.yml ├── index.js └── README.md /screenshot.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kibibit/kb-badger-action/main/screenshot.jpg -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@kibibit/kb-badger-action", 3 | "version": "1.0.0", 4 | "description": "When an Environment is deployed successfully, add a badge to PR body with links to environment", 5 | "main": "index.js", 6 | "scripts": { 7 | "build": "ncc build index.js --license LICENSE", 8 | "test": "echo \"Error: no test specified\" && exit 1" 9 | }, 10 | "repository": { 11 | "type": "git", 12 | "url": "git+https://github.com/Kibibit/kb-badger.git" 13 | }, 14 | "author": "thatkookooguy ", 15 | "license": "MIT", 16 | "bugs": { 17 | "url": "https://github.com/Kibibit/kb-badger/issues" 18 | }, 19 | "homepage": "https://github.com/Kibibit/kb-badger#readme", 20 | "publishConfig": { 21 | "access": "public" 22 | }, 23 | "dependencies": { 24 | "@actions/core": "^1.2.7", 25 | "@actions/github": "^4.0.0", 26 | "lodash": "^4.17.21", 27 | "url-join": "^4.0.1" 28 | }, 29 | "devDependencies": { 30 | "@types/node": "^15.0.1", 31 | "@vercel/ncc": "^0.28.4" 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 kibibit 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 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | lerna-debug.log* 8 | 9 | # Diagnostic reports (https://nodejs.org/api/report.html) 10 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 11 | 12 | # Runtime data 13 | pids 14 | *.pid 15 | *.seed 16 | *.pid.lock 17 | 18 | # Directory for instrumented libs generated by jscoverage/JSCover 19 | lib-cov 20 | 21 | # Coverage directory used by tools like istanbul 22 | coverage 23 | *.lcov 24 | 25 | # nyc test coverage 26 | .nyc_output 27 | 28 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 29 | .grunt 30 | 31 | # Bower dependency directory (https://bower.io/) 32 | bower_components 33 | 34 | # node-waf configuration 35 | .lock-wscript 36 | 37 | # Compiled binary addons (https://nodejs.org/api/addons.html) 38 | build/Release 39 | 40 | # Dependency directories 41 | jspm_packages/ 42 | 43 | # Snowpack dependency directory (https://snowpack.dev/) 44 | web_modules/ 45 | 46 | # TypeScript cache 47 | *.tsbuildinfo 48 | 49 | # Optional npm cache directory 50 | .npm 51 | 52 | # Optional eslint cache 53 | .eslintcache 54 | 55 | # Microbundle cache 56 | .rpt2_cache/ 57 | .rts2_cache_cjs/ 58 | .rts2_cache_es/ 59 | .rts2_cache_umd/ 60 | 61 | # Optional REPL history 62 | .node_repl_history 63 | 64 | # Output of 'npm pack' 65 | *.tgz 66 | 67 | # Yarn Integrity file 68 | .yarn-integrity 69 | 70 | # dotenv environment variables file 71 | .env 72 | .env.test 73 | 74 | # parcel-bundler cache (https://parceljs.org/) 75 | .cache 76 | .parcel-cache 77 | 78 | # Next.js build output 79 | .next 80 | out 81 | 82 | # Nuxt.js build / generate output 83 | .nuxt 84 | 85 | # Gatsby files 86 | .cache/ 87 | # Comment in the public line in if your project uses Gatsby and not Next.js 88 | # https://nextjs.org/blog/next-9-1#public-directory-support 89 | # public 90 | 91 | # vuepress build output 92 | .vuepress/dist 93 | 94 | # Serverless directories 95 | .serverless/ 96 | 97 | # FuseBox cache 98 | .fusebox/ 99 | 100 | # DynamoDB Local files 101 | .dynamodb/ 102 | 103 | # TernJS port file 104 | .tern-port 105 | 106 | # Stores VSCode versions used for testing VSCode extensions 107 | .vscode-test 108 | 109 | # yarn v2 110 | .yarn/cache 111 | .yarn/unplugged 112 | .yarn/build-state.yml 113 | .yarn/install-state.gz 114 | .pnp.* 115 | -------------------------------------------------------------------------------- /action.yml: -------------------------------------------------------------------------------- 1 | name: 'Kb Pull Request Deployment Badges' 2 | description: 'Add deployment links to PR body as badges' 3 | branding: 4 | icon: 'bell' 5 | color: 'blue' 6 | inputs: 7 | github-token: 8 | description: The GitHub token used to create an authenticated client 9 | default: ${{ github.token }} 10 | required: false 11 | position: 12 | description: | 13 | Where should the tags be positioned? If tag definitions are found, 14 | they will just be replaced. This allows you to position them anywhere 15 | if you add them to your PR template 16 | default: top 17 | required: false 18 | separator: 19 | description: Should we add a seperator between badges and pr body content? 20 | default: 'true' 21 | required: false 22 | style: 23 | description: Badge style. Can be one of `plastic` | `flat` | `flat-square` | `for-the-badge` | `social` 24 | required: false 25 | badge-left: 26 | description: Left-Hand side text of badge 27 | required: false 28 | default: demo 29 | badge-right: 30 | description: Right-Hand side text of badge 31 | required: false 32 | default: application 33 | badge-color: 34 | description: Right-Hand side css color 35 | required: false 36 | default: informational 37 | badge-logo: 38 | description: Badge Logo 39 | required: false 40 | default: '' 41 | badge-path: 42 | description: Path to navigate to in deployment 43 | required: false 44 | default: '' 45 | badge2-left: 46 | description: Left-Hand side text of badge 47 | required: false 48 | badge2-right: 49 | description: Right-Hand side text of badge 50 | required: false 51 | badge2-color: 52 | description: Right-Hand side css color 53 | required: false 54 | badge2-logo: 55 | description: Badge Logo 56 | required: false 57 | badge2-path: 58 | description: Path to navigate to in deployment 59 | required: false 60 | badge3-left: 61 | description: Left-Hand side text of badge 62 | required: false 63 | badge3-right: 64 | description: Right-Hand side text of badge 65 | required: false 66 | badge3-color: 67 | description: Right-Hand side css color 68 | required: false 69 | badge3-logo: 70 | description: Badge Logo 71 | required: false 72 | badge3-path: 73 | description: Path to navigate to in deployment 74 | required: false 75 | outputs: 76 | time: # id of output 77 | description: The time the action finished 78 | runs: 79 | using: 'node12' 80 | main: 'index.js' -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | // @ts-check 2 | const core = require('@actions/core'); 3 | const github = require('@actions/github'); 4 | const _ = require('lodash'); 5 | const urljoin = require('url-join'); 6 | 7 | const style = core.getInput('style'); 8 | const badgeTemplate = _.template( 9 | [ 10 | '[![kb-badger-action--<%= name %>]', 11 | '(https://img.shields.io/badge/<%= left %>-<%= right %>-<%= color %>', 12 | `?logo=<%= logo %>${ style ? '&style=' + style : '' })]`, 13 | '(<%= url %>)' 14 | ].join('') 15 | ); 16 | 17 | (async () => { 18 | try { 19 | const token = core.getInput('github-token', { required: true }); 20 | const shouldAddSeperator = core.getInput('separator').toLowerCase() === 'true'; 21 | const position = core.getInput('position'); 22 | const octokit = github.getOctokit(token); 23 | const { context } = github; 24 | const DEPLOYMENT_URL = github.context.payload.deployment_status.deployment_url; 25 | const owner = context.repo.owner; 26 | const repo = context.repo.repo; 27 | console.log(`processing deployment: ${ DEPLOYMENT_URL }`); 28 | const deploymentData = await getDeploymentData(); 29 | 30 | // create the 3 badges data array 31 | const badgesData = []; 32 | ['badge3', 'badge2', 'badge'].forEach((name) => { 33 | const badge = getBadgeDefinition(name); 34 | if (badge) { 35 | badgesData.push(badge); 36 | } 37 | }); 38 | 39 | // for each pull request reference containing the commit that got deployed, 40 | // change or add the badges based on the configuration 41 | for (const prRef of deploymentData.prRefs) { 42 | if (prRef) { 43 | const prId = +prRef.replace('refs/pull/', '').replace('/head', ''); 44 | console.log(`Found Pull-Request pointing to the deployment: ${ owner }/${ repo }/${ prId }`); 45 | const pr = await octokit.pulls.get({ 46 | owner, 47 | repo, 48 | pull_number: prId 49 | }); 50 | let body = pr.data.body; 51 | 52 | badgesData.forEach((badgeData, index) => { 53 | const compiledBadge = badgeTemplate(badgeData); 54 | console.log(badgeData); 55 | if (pr.data.body.includes(badgeData.badgeId)) { 56 | console.log('Badge exists in PR body. Replacing with newer version'); 57 | // replace badge 58 | body = body.replace(badgeData.badgeCatchRegex, compiledBadge); 59 | } else { 60 | console.log(`Badge Not found. Adding new one to body at ${ position }`); 61 | const seperator = shouldAddSeperator ? 62 | `${position === 'top' ? '\n' : '\n\n'}-----${position !== 'top' ? '\n' : '\n\n'}` : 63 | '\n\n'; 64 | 65 | const newBody = [ 66 | compiledBadge, 67 | index === 0 ? seperator : ' ', 68 | `${body}` 69 | ]; 70 | 71 | if (position === 'top') { 72 | // add badge 73 | body = newBody.join(''); 74 | } else { 75 | // add badge 76 | body = newBody.reverse().join(''); 77 | } 78 | } 79 | }); 80 | 81 | await octokit.pulls.update({ 82 | owner, 83 | repo, 84 | pull_number: prId, 85 | body 86 | }); 87 | } 88 | } 89 | 90 | const time = (new Date()).toTimeString(); 91 | core.setOutput("time", time); 92 | 93 | async function getDeploymentData() { 94 | const deployment_id = +DEPLOYMENT_URL.replace(/^.*deployments\//, ''); 95 | const deployment = await octokit.repos.getDeployment({ 96 | owner, 97 | repo, 98 | deployment_id 99 | }); 100 | const envUrl = deployment.data.payload.web_url; 101 | const commitRef = deployment.data.ref; 102 | const refs = await octokit.git.listMatchingRefs({ 103 | owner, 104 | repo, 105 | ref: undefined 106 | }); 107 | const prRefs = refs.data 108 | .filter((ref) => ref.object.sha === commitRef && ref.ref.startsWith('refs/pull/')) 109 | .map((prRef) => prRef.ref); 110 | 111 | return { 112 | envUrl, 113 | prRefs 114 | }; 115 | } 116 | 117 | function getBadgeDefinition(name) { 118 | const left = encodeURIComponent(core.getInput(`${name}-left`).replace('-', '--').replace('_', '__')); 119 | const right = encodeURIComponent(core.getInput(`${name}-right`).replace('-', '--').replace('_', '__')); 120 | const color = core.getInput(`${name}-color`); 121 | const urlPath = core.getInput(`${name}-path`); 122 | const logo = core.getInput(`${name}-logo`); 123 | const badgeId = `kb-badger-action--${name}`; 124 | const badgeCatchRegex = new RegExp(`\\[!\\[${badgeId}\\]\\(.*?\\)]\\(.*?\\)`); 125 | 126 | if (!left) { 127 | return; 128 | } 129 | 130 | return { 131 | name, 132 | left, 133 | right, 134 | color, 135 | url: urljoin(deploymentData.envUrl, urlPath), 136 | logo, 137 | badgeCatchRegex, 138 | badgeId 139 | }; 140 | } 141 | 142 | } catch (error) { 143 | core.setFailed(error.message); 144 | } 145 | })(); -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | achievibit Logo 3 | 4 |

5 | @kibibit/kb-badger-action 6 |

7 |

8 |

9 | All Contributors 10 |

11 |

12 | 13 | All Contributors 14 | 15 |

16 |

17 | Add deployment baddges to Pull-Requests 18 |

19 |
20 | 21 | When an Environment is deployed successfully, add a badge to PR body with links to the environment 22 | 23 | screenshot 24 | 25 | --- 26 | 27 | Uses [shields.io](https://shields.io/) to generate shields. Check out their documentation for color support and more. 28 | 29 | Use [simple-icons](https://simpleicons.org/) for logo names. Logos are referenced using names as they appear on the simple-icons site. If the name includes spaces, replace them with dashes (e.g: `visual-studio-code`) 30 | 31 | ## Inputs 32 | 33 | ### General 34 | 35 | | Input Name | Description | Default | Required | 36 | | :-------------: | :------------- | :-------------: | :-------------: | 37 | | github-token | The GitHub token used to create an authenticated client | `${{ github.token }}` | ✔️ | 38 | | position | Where should the tags be positioned? If tag definitions are found, they will just be replaced. This allows you to position them anywhere if you add them to your PR template | `top` | ❌ | 39 | | separator | Should we add a seperator between badges and pr body content? | `true` | ❌ | 40 | | style | Badge style. Can be one of `plastic` \| `flat` \| `flat-square` \| `for-the-badge` \| `social` | `flat` | ❌ | 41 | 42 | ### Badge Settings 43 | 44 | | Input Name | Description | Default | Required | 45 | | :-------------: | :------------- | :-------------: | :-------------: | 46 | | badge-left | Left-Hand side text of badge | `demo` | ❌ | 47 | | badge-right | Right-Hand side text of badge | `application` | ❌ | 48 | | badge-color | Right-Hand side shield color | `informational` | ❌ | 49 | | badge-logo | Badge Logo | `undefined` | ❌ | 50 | | badge-path | Path to navigate to in deployment. If not set, the deployment url will be used as-is | `undefined` | ❌ | 51 | 52 | These inputs are repeated 3 tims to support 3 simultanous badges. 53 | 54 | - badge - badge-left, badge-right... 55 | - badge2 - badge2-left, badge2-right... 56 | - badge3 - badge3-left, badge3-right... 57 | 58 | ## Outputs 59 | 60 | After adding the PR badge, the action outputs the time it finished 61 | 62 | ## Example usage 63 | ```yaml 64 | name: Add PR Deploy Badges 65 | # https://docs.github.com/en/actions/reference/events-that-trigger-workflows 66 | on: [deployment_status] 67 | 68 | jobs: 69 | badge: 70 | runs-on: ubuntu-latest 71 | permissions: 72 | pull-requests: write 73 | # only runs this job on successful deploy 74 | if: github.event.deployment_status.state == 'success' 75 | steps: 76 | - name: TEST KB-BADGER-ACTION 77 | uses: kibibit/kb-badger-action@v1.91 78 | with: 79 | style: for-the-badge 80 | github-token: ${{secrets.GITHUB_TOKEN}} 81 | badge-left: demo 82 | badge-right: application 83 | badge-logo: heroku 84 | badge-path: api 85 | badge2-left: demo 86 | badge2-right: api-docs 87 | badge2-color: 85EA2D 88 | badge2-logo: swagger 89 | badge2-path: api/docs 90 | ``` 91 | 92 | ## Contributors ✨ 93 | 94 | Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/docs/en/emoji-key)): 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 |

Neil Kalman

💻 📖 🎨 🚧 🚇 ⚠️
103 | 104 | 105 | 106 | 107 | 108 | 109 | This project follows the [all-contributors](https://github.com/all-contributors/all-contributors) specification. Contributions of any kind are welcome! 110 | 111 |
Icons made by Freepik from www.flaticon.com
112 | 113 | ## Stay in touch 114 | 115 | - Author - [Neil Kalman](https://github.com/thatkookooguy) 116 | - Website - [https://github.com/kibibit](https://github.com/kibibit) 117 | - StackOverflow - [thatkookooguy](https://stackoverflow.com/users/1788884/thatkookooguy) 118 | - Twitter - [@thatkookooguy](https://twitter.com/thatkookooguy) 119 | - Twitter - [@kibibit_opensrc](https://twitter.com/kibibit_opensrc) 120 | --------------------------------------------------------------------------------