1 |
|
2 |
|
3 |
|
4 |
|
5 |
|
6 |
|
7 |
|
8 |
|
9 | var shell = require('shelljs');
|
10 | var semver = require('semver');
|
11 | var request = require('superagent');
|
12 | var Q = require('q');
|
13 |
|
14 | module.exports = function(grunt){
|
15 | grunt.registerTask('release', 'bump version, git tag, git push, npm publish', function(type){
|
16 |
|
17 |
|
18 | var options = grunt.util._.extend({
|
19 | bump: true,
|
20 |
|
21 | file: grunt.config('pkgFile') || 'package.json',
|
22 |
|
23 | additionalFiles: [],
|
24 | add: true,
|
25 | commit: true,
|
26 | tag: true,
|
27 | push: true,
|
28 | pushTags: true,
|
29 | npm : true,
|
30 | remote: "origin"
|
31 | }, grunt.config(this.name).options);
|
32 |
|
33 | var config = setup(options.file, type);
|
34 | var templateOptions = {
|
35 | data: {
|
36 | name: config.pkg.name,
|
37 | version: config.newVersion
|
38 | }
|
39 | };
|
40 |
|
41 | var tagName = grunt.template.process(grunt.config.getRaw(this.name + '.options.tagName') || '<%= version %>', templateOptions);
|
42 | var commitMessage = grunt.template.process(grunt.config.getRaw(this.name + '.options.commitMessage') || 'release <%= version %>', templateOptions);
|
43 | var tagMessage = grunt.template.process(grunt.config.getRaw(this.name + '.options.tagMessage') || 'version <%= version %>', templateOptions);
|
44 |
|
45 | var nowrite = grunt.option('no-write');
|
46 | var indentation = grunt.option('indentation') || ' ';
|
47 | var task = this;
|
48 | var done = this.async();
|
49 |
|
50 | if (!config.newVersion) {
|
51 | grunt.warn("Resulting version number is empty.");
|
52 | }
|
53 |
|
54 |
|
55 | if (nowrite){
|
56 | grunt.log.ok('-------RELEASE DRY RUN-------');
|
57 | }
|
58 |
|
59 | Q()
|
60 | .then(ifEnabled('bump', bump))
|
61 | .then(ifEnabled('add', add))
|
62 | .then(ifEnabled('commit', commit))
|
63 | .then(ifEnabled('tag', tag))
|
64 | .then(ifEnabled('push', push))
|
65 | .then(ifEnabled('pushTags', pushTags))
|
66 | .then(ifEnabled('npm', publish))
|
67 | .then(ifEnabled('github', githubRelease))
|
68 | .catch(function(msg){
|
69 | grunt.fail.warn(msg || 'release failed')
|
70 | })
|
71 | .finally(done);
|
72 |
|
73 |
|
74 | function setup(file, type){
|
75 | var pkg = grunt.file.readJSON(file);
|
76 | var newVersion = pkg.version;
|
77 | if (options.bump) {
|
78 | newVersion = semver.inc(pkg.version, type || 'patch');
|
79 | }
|
80 | options.additionalFiles.push(file);
|
81 | return {files: options.additionalFiles, newVersion: newVersion};
|
82 | }
|
83 |
|
84 | function getNpmTag(){
|
85 | var tag = grunt.option('npmtag') || options.npmtag;
|
86 | if(tag === true) { tag = config.newVersion; }
|
87 | return tag;
|
88 | }
|
89 |
|
90 | function ifEnabled(option, fn){
|
91 | if (options[option]) return fn;
|
92 | }
|
93 |
|
94 | function run(cmd, msg){
|
95 | var deferred = Q.defer();
|
96 | grunt.verbose.writeln('Running: ' + cmd);
|
97 |
|
98 | if (nowrite) {
|
99 | grunt.log.ok(msg || cmd);
|
100 | deferred.resolve();
|
101 | }
|
102 | else {
|
103 | var success = shell.exec(cmd, {silent:true}).code === 0;
|
104 |
|
105 | if (success){
|
106 | grunt.log.ok(msg || cmd);
|
107 | deferred.resolve();
|
108 | }
|
109 | else{
|
110 |
|
111 | deferred.reject('Failed when executing: `' + cmd + '`\n');
|
112 | }
|
113 | }
|
114 | return deferred.promise;
|
115 | }
|
116 |
|
117 | function add(){
|
118 | var files = config.files.join(' ');
|
119 | return run('git add ' + files, ' staged ' + files);
|
120 | }
|
121 |
|
122 | function commit(){
|
123 | if (typeof commitMessage === 'string') {
|
124 | commitMessage = [commitMessage];
|
125 | }
|
126 | var message = commitMessage.map(function(el) {
|
127 | return '-m "' + grunt.template.process(el, templateOptions) + '"';
|
128 | }).join(' ');
|
129 |
|
130 | return run('git commit '+ config.file + ' ' + message, config.file + ' committed');
|
131 | }
|
132 |
|
133 | function tag(){
|
134 | return run('git tag ' + tagName + ' -m "'+ tagMessage +'"', 'created new git tag: ' + tagName);
|
135 | }
|
136 |
|
137 | function push(){
|
138 | run('git push ' + config.remote + ' HEAD', 'pushed to remote');
|
139 | }
|
140 |
|
141 | function pushTags(config){
|
142 | run('git push ' + config.remote + ' ' + tagName, 'pushed new tag '+ config.newVersion +' to remote');
|
143 | }
|
144 |
|
145 | function publish(){
|
146 | var cmd = 'npm publish';
|
147 | var msg = 'published version '+ config.newVersion +' to npm';
|
148 | var npmtag = getNpmTag();
|
149 | if (npmtag){
|
150 | cmd += ' --tag ' + npmtag;
|
151 | msg += ' with a tag of "' + npmtag + '"';
|
152 | }
|
153 |
|
154 | if (options.folder){ cmd += ' ' + options.folder; }
|
155 | return run(cmd, msg);
|
156 | }
|
157 |
|
158 | function bump(){
|
159 | var i, l, file, pkg, promise;
|
160 | var promises = [];
|
161 | for (i = 0, l = config.files.length ; i < l ; i++) {
|
162 | file = config.files[i];
|
163 | promise = (function(file){
|
164 | return Q.fcall(function () {
|
165 | pkg = grunt.file.readJSON(file);
|
166 | pkg.version = config.newVersion;
|
167 | grunt.file.write(file, JSON.stringify(pkg, null, indentation) + '\n');
|
168 | grunt.log.ok('bumped version of ' + file + ' to ' + config.newVersion);
|
169 | });
|
170 | }(file));
|
171 | promises.push(promise);
|
172 | }
|
173 | return Q.all(promises);
|
174 | }
|
175 |
|
176 | function githubRelease(){
|
177 | var deferred = Q.defer();
|
178 | if (nowrite){
|
179 | success();
|
180 | return;
|
181 | }
|
182 |
|
183 | request
|
184 | .post('https://api.github.com/repos/' + options.github.repo + '/releases')
|
185 | .auth(process.env[options.github.usernameVar], process.env[options.github.passwordVar])
|
186 | .set('Accept', 'application/vnd.github.manifold-preview')
|
187 | .set('User-Agent', 'grunt-release')
|
188 | .send({"tag_name": tagName, "name": tagMessage})
|
189 | .end(function(res){
|
190 | if (res.statusCode === 201){
|
191 | success();
|
192 | }
|
193 | else {
|
194 | deferred.reject('Error creating github release. Response: ' + res.text);
|
195 | }
|
196 | });
|
197 |
|
198 | function success(){
|
199 | grunt.log.ok('created ' + tagName + ' release on github.');
|
200 | deferred.resolve();
|
201 | }
|
202 |
|
203 | return deferred.promise;
|
204 | }
|
205 |
|
206 | });
|
207 | };
|