Discover innovative tools, design features, unique websites, and code structure curated by the GoWeb community.
View the resources by category or by monthly GeekOut! Compilations. Check out the latest compilation below.
< Back to
Continuous Release on Github with Grunt
TL:DR; Just show me the Gruntfile!
This post will describe the release tasks I’ve written for Grunt which work seamlessly with our existing continuous deployment pipeline on Codeship. I also mention a step you can take if you want to do this directly from your command line instead.
I help develop WordPress themes and plugins for Texas A&M AgriLife, which are available on Github. Earlier this year, I was asked to develop and implement a continuous deployment pipeline for most of our repositories. I wrote an article about it here. Long story short, we use Codeship to watch our repository branches (staging and master) and run console commands to prep and push them to the staging and production versions of our servers.
I was recently asked to create a release for our latest theme AgriFlex3, and in hope I looked for articles on how to do so with Grunt. I soon found this article by Paul C. Pederson which showed me this approach was possible, but I needed something a little different. What follows are Grunt modules and commands based on those I use to package and release our open-source projects on Github with a release message that describes all of the commits made since the previous release. These tasks can be run for any project, not just WordPress!
grunt-contrib-compress
This is the simplest task. You provide the archive file name and an array of file globs to copy into it. We try to use values from package.json when we can. View the grunt-contrib-compress repository for more information.
compress: main: options: archive: '<%= pkg.name %>.zip' files: [ {src: ['css/*.css']}, {src: ['img/**']}, {src: ['js/*.js']}, {src: ['**/*.php']}, {src: ['*.php']}, {src: ['README.md']}, {src: ['screenshot.png']} ]
grunt-gh-release
This task creates our release using properties within package.json, a Github access token available in our Codeship environment, and a few custom tasks (shown in the next section) that populate the release message beforehand. If you want to run your release task locally then use the documentation’s method for the options token instead of what is shown here. View the grunt-gh-release repository for more information.
gh_release: options: token: process.env.GITHUB_PERSONAL_ACCESS_TOKEN owner: 'agrilife' repo: '<%= pkg.name %>' release: tag_name: '<%= pkg.version %>' target_commitish: 'master' name: 'Release' body: 'First release' draft: false prerelease: false asset: name: '<%= pkg.name %>.zip' file: '<%= pkg.name %>.zip' 'Content-Type': 'application/zip'
Custom tasks
The two custom tasks I’ve written will run console commands and handle their output. The first task gets the last release tag and uses it to set a config value as the range of commit messages we want to retrieve. The second task takes this range and uses it to populate the release message with a bulleted list of commits below their author’s name. This is necessary because the github token is linked to a person and that name shows near release messages. We want to ensure they are not seen by users as the author of all released commits.
@registerTask 'setreleasemsg', 'Set release message as range of commits', -> done = @async() grunt.util.spawn { cmd: 'git' args: [ 'tag' ] }, (err, result, code) -> if(result.stdout!='') # Get last tag in the results matches = result.stdout.match(/([^\n]+)$/) # Set commit message timeline releaserange = matches[1] + '..HEAD' grunt.config.set 'releaserange', releaserange # Run the next task grunt.task.run('shortlog'); done(err) return return @registerTask 'shortlog', 'Set gh_release body with commit messages since last release', -> done = @async() grunt.util.spawn { cmd: 'git' args: ['shortlog', grunt.config.get('releaserange'), '--no-merges'] }, (err, result, code) -> if(result.stdout != '') # Hyphenate commit messages message = result.stdout.replace(/(\n)\s\s+/g, '$1- ') # Set release message grunt.config 'gh_release.release.body', message else # Just in case merges are the only commit grunt.config 'gh_release.release.body', 'release' done(err) return return
Gruntfile.coffee
module.exports = (grunt) -> @initConfig pkg: @file.readJSON('package.json') compress: main: options: archive: '<%= pkg.name %>.zip' files: [ {src: ['css/*.css']}, {src: ['img/**']}, {src: ['js/*.js']}, {src: ['**/*.php']}, {src: ['*.php']}, {src: ['README.md']}, {src: ['screenshot.png']} ] gh_release: options: token: process.env.GITHUB_PERSONAL_ACCESS_TOKEN owner: 'agrilife' repo: '<%= pkg.name %>' release: tag_name: '<%= pkg.version %>' target_commitish: 'master' name: 'Release' body: 'First release' draft: false prerelease: false asset: name: '<%= pkg.name %>.zip' file: '<%= pkg.name %>.zip' 'Content-Type': 'application/zip' @loadNpmTasks 'grunt-contrib-compress' @loadNpmTasks 'grunt-gh-release' @registerTask 'release', ['compress', 'setreleasemsg', 'gh_release'] @registerTask 'setreleasemsg', 'Set release message as range of commits', -> done = @async() grunt.util.spawn { cmd: 'git' args: [ 'tag' ] }, (err, result, code) -> if(result.stdout!='') # Get last tag in the results matches = result.stdout.match(/([^\n]+)$/) # Set commit message timeline releaserange = matches[1] + '..HEAD' grunt.config.set 'releaserange', releaserange # Run the next task grunt.task.run('shortlog'); done(err) return return @registerTask 'shortlog', 'Set gh_release body with commit messages since last release', -> done = @async() grunt.util.spawn { cmd: 'git' args: ['shortlog', grunt.config.get('releaserange'), '--no-merges'] }, (err, result, code) -> if(result.stdout != '') # Hyphenate commit messages message = result.stdout.replace(/(\n)\s\s+/g, '$1- ') # Set release message grunt.config 'gh_release.release.body', message else # Just in case merges are the only commit grunt.config 'gh_release.release.body', 'release' done(err) return return
If this helps you, or if you have any suggestions for improvements, please leave a comment!