├── .gitignore ├── .travis.yml ├── app.json ├── docs └── deploy.md ├── package.json ├── LICENSE ├── README.md └── index.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | npm-debug.log 3 | *.pem 4 | .env 5 | package-lock.json 6 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | language: node_js 3 | node_js: 4 | - "7.7.1" 5 | notifications: 6 | disabled: true 7 | -------------------------------------------------------------------------------- /app.json: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "PRIVATE_KEY": { 4 | "description": "the private key you downloaded when creating the GitHub App" 5 | }, 6 | "APP_ID": { 7 | "description": "the ID of your GitHub App" 8 | }, 9 | "WEBHOOK_SECRET": { 10 | "description": "the secret configured for your GitHub App" 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /docs/deploy.md: -------------------------------------------------------------------------------- 1 | # Deploying 2 | 3 | If you would like to run your own instance of this app, see the [docs for deployment](https://probot.github.io/docs/deployment/). 4 | 5 | This app requires these **Permissions & events** for the GitHub App: 6 | 7 | - Issues - **Read & Write** 8 | - Repository contents - **Read-only** 9 | - [x] Check the box for **Create** events 10 | - [x] Check the box for **Release** events 11 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "probot-semver", 3 | "version": "1.0.0", 4 | "description": "A Probot app that provides automatic semantic versioning support", 5 | "author": "John Blackbourn ", 6 | "license": "ISC", 7 | "repository": "https://github.com/johnbillion/probot-semver.git", 8 | "scripts": { 9 | "start": "probot run ./index.js", 10 | "test": "mocha && standard" 11 | }, 12 | "dependencies": { 13 | "probot": "^2.0.0" 14 | }, 15 | "devDependencies": { 16 | "expect": "^1.20.2", 17 | "localtunnel": "^1.8.2", 18 | "mocha": "^3.2.0", 19 | "standard": "^10.0.3" 20 | }, 21 | "engines": { 22 | "node": ">= 7.7.0", 23 | "npm": ">= 4.0.0" 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | ISC License 2 | 3 | Copyright (c) 2017, John Blackbourn 4 | 5 | Permission to use, copy, modify, and/or distribute this software for any 6 | purpose with or without fee is hereby granted, provided that the above 7 | copyright notice and this permission notice appear in all copies. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 10 | WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 11 | MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 12 | ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 13 | WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 14 | ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 15 | OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 16 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Probot semver 2 | 3 | > A GitHub app built with [Probot](https://github.com/probot/probot) that provides automatic semantic versioning support. 4 | 5 | ## Features 6 | 7 | When you push a tag: 8 | 9 | * If there are open issues in the corresponding milestone, an issue detailing them is automatically created and assigned to the user who pushed the tag. 10 | 11 | When you publish a [release](https://help.github.com/articles/creating-releases/): 12 | 13 | * The milestone for the release is automatically closed. 14 | * Milestones for the next major, minor, and patch releases are automatically created, if they don't yet exist. 15 | 16 | ## Installation 17 | 18 | This can be installed on your repository as a GitHub App: https://github.com/apps/probot-semver 19 | 20 | ## Running Your Own Instance 21 | 22 | See [docs/deploy.md](docs/deploy.md) if you would like to run your own instance of this app. 23 | 24 | ``` 25 | # Install dependencies 26 | npm install 27 | 28 | # Run the bot 29 | npm start 30 | ``` 31 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | module.exports = (robot) => { 2 | console.log( 'Welcome to Probot semver' ); 3 | 4 | robot.on( 'release.published', release_published ); 5 | robot.on( 'create', pointer_created ); 6 | 7 | async function pointer_created( context ) { 8 | var github = context.github; 9 | var owner, repo; 10 | 11 | [owner,repo] = context.payload.repository.full_name.split( '/' ); 12 | 13 | if ( 'tag' === context.payload.ref_type ) { 14 | // A tag was created. 15 | console.log( context.payload.sender.login + ' tagged ' + owner + '/' + repo + ' ' + context.payload.ref + '!' ); 16 | 17 | // probot semver expects a semver string to be present in the tag, with an optional prefix 18 | var matches = context.payload.ref.match( /[0-9]+\.[0-9]+\.[0-9]+$/ ); 19 | 20 | if ( ! matches ) { 21 | console.log('Could not detect a semver :('); 22 | return; 23 | } 24 | var ver = matches[0]; 25 | 26 | // Fetch all milestones 27 | github.issues.getMilestones( { 28 | 'owner' : owner, 29 | 'repo' : repo, 30 | 'state' : 'open', 31 | }, function( error, result ) { 32 | var ver_ms = null; 33 | 34 | // with no milestones, result is an empty array 35 | if ( result && result.data ) { 36 | for ( var i in result.data ) { 37 | // Determine matching milestone 38 | if ( result.data[i].title == ver ) { 39 | ver_ms = result.data[i].number; 40 | break; 41 | } 42 | } 43 | 44 | if ( ! ver_ms ) { 45 | // nothing to do here 46 | return; 47 | } 48 | 49 | // Fetch all open issues for milestone 50 | github.issues.getForRepo({ 51 | 'owner' : owner, 52 | 'repo' : repo, 53 | 'state' : 'open', 54 | 'milestone': ver_ms, 55 | 'sort' : 'created', 56 | 'direction': 'asc', 57 | }, function( error, result ) { 58 | if ( result && result.data && result.data.length ) { 59 | // There are open issues in the milestone 60 | // Open an issue 61 | // - list references and titles to the open issues 62 | // - assign to the user who pushed the tag 63 | // - place in the milestone that was just pushed 64 | 65 | var body = ''; 66 | var issue = null; 67 | 68 | for ( var i in result.data ) { 69 | issue = result.data[i]; 70 | body += '* #' + issue.number + ': ' + issue.title + '\n'; 71 | } 72 | 73 | var tag_url = context.payload.repository.html_url + '/releases/tag/' + context.payload.ref; 74 | 75 | body = '[' + context.payload.ref + '](' + tag_url + ') was tagged by @' + context.payload.sender.login + ' but the following tickets in the milestone are still open:\n\n' + body; 76 | 77 | github.issues.create({ 78 | 'owner' : owner, 79 | 'repo' : repo, 80 | 'title' : 'There are open issues in the ' + ver + ' milestone', 81 | 'body' : body, 82 | 'assignee' : context.payload.sender.login, 83 | 'milestone': ver_ms, 84 | }, function(){ 85 | console.log('Issue created'); 86 | }); 87 | } 88 | }); 89 | } 90 | } ); 91 | 92 | } 93 | } 94 | 95 | async function release_published( context ) { 96 | var release = context.payload.release; 97 | var github = context.github; 98 | var owner, repo; 99 | 100 | [owner,repo] = context.payload.repository.full_name.split( '/' ); 101 | 102 | console.log( release.author.login + ' released ' + owner + '/' + repo + ' ' + release.tag_name + '!' ); 103 | 104 | // probot semver expects a semver string to be present in the tag, with an optional prefix 105 | var matches = release.tag_name.match( /[0-9]+\.[0-9]+\.[0-9]+$/ ); 106 | 107 | if ( ! matches ) { 108 | console.log('Could not detect a semver :('); 109 | return; 110 | } 111 | var ver = matches[0]; 112 | var major, minor, patch; 113 | 114 | [major,minor,patch] = ver.split( '.' ); 115 | 116 | var next_major = ( parseInt( major, 10 ) + 1 ) + '.0.0'; 117 | var next_minor = major + '.' + ( parseInt( minor, 10 ) + 1 ) + '.0'; 118 | var next_patch = major + '.' + minor + '.' + ( parseInt( patch, 10 ) + 1 ); 119 | 120 | github.issues.getMilestones( { 121 | 'owner' : owner, 122 | 'repo' : repo, 123 | 'state' : 'open', 124 | }, function( error, result ) { 125 | var create_major = true; 126 | var create_minor = true; 127 | var create_patch = true; 128 | var ver_ms = null; 129 | 130 | // with no milestones, result is an empty array 131 | if ( result && result.data ) { 132 | for ( var i in result.data ) { 133 | if ( result.data[i].title == next_major ) { 134 | create_major = false; 135 | } 136 | if ( result.data[i].title == next_minor ) { 137 | create_minor = false; 138 | } 139 | if ( result.data[i].title == next_patch ) { 140 | create_patch = false; 141 | } 142 | if ( result.data[i].title == ver ) { 143 | ver_ms = result.data[i].number; 144 | } 145 | } 146 | } 147 | 148 | if ( ver_ms ) { 149 | console.log('Closing current milestone: '+ver); 150 | github.issues.updateMilestone( { 151 | 'owner' : owner, 152 | 'repo' : repo, 153 | 'number' : ver_ms, 154 | 'state' : 'closed', 155 | 'title' : ver, 156 | } ); 157 | } else { 158 | console.log( 'Current release milestone does not exist' ); 159 | } 160 | 161 | if ( create_major ) { 162 | console.log('Creating next major milestone: ' + next_major); 163 | github.issues.createMilestone( { 164 | 'owner' : owner, 165 | 'repo' : repo, 166 | 'state' : 'open', 167 | 'title' : next_major, 168 | } ); 169 | } else { 170 | console.log( 'Next major milestone already exists: ' + next_major ); 171 | } 172 | 173 | if ( create_minor ) { 174 | console.log('Creating next minor milestone: ' + next_minor); 175 | github.issues.createMilestone( { 176 | 'owner' : owner, 177 | 'repo' : repo, 178 | 'state' : 'open', 179 | 'title' : next_minor, 180 | } ); 181 | } else { 182 | console.log( 'Next minor milestone already exists: ' + next_minor ); 183 | } 184 | 185 | if ( create_patch ) { 186 | console.log('Creating next patch milestone: ' + next_patch); 187 | github.issues.createMilestone( { 188 | 'owner' : owner, 189 | 'repo' : repo, 190 | 'state' : 'open', 191 | 'title' : next_patch, 192 | } ); 193 | } else { 194 | console.log( 'Next patch milestone already exists: ' + next_patch ); 195 | } 196 | } ); 197 | 198 | } 199 | } 200 | --------------------------------------------------------------------------------