1 | #!/usr/bin/env node
|
2 |
|
3 |
|
4 |
|
5 |
|
6 | var fs = require('fs');
|
7 | var path = require('path');
|
8 |
|
9 | var _ = require('lodash');
|
10 | var S = require('string');
|
11 | var shell = require('shelljs');
|
12 | var Mustache = require('mustache');
|
13 | var semver = require('semver');
|
14 | var program = require('commander');
|
15 | var Promise = require('bluebird');
|
16 |
|
17 |
|
18 |
|
19 | var config = {
|
20 | releaseMessage: 'Release {{ version }}',
|
21 | backToDevMessage: 'Bump to dev version',
|
22 | bumpType: 'patch',
|
23 | files: ['package.json'],
|
24 | readmeFile: 'README.md',
|
25 | indentation: 4,
|
26 |
|
27 |
|
28 | dryRun: false,
|
29 |
|
30 |
|
31 | noPush: false,
|
32 | consolePrefix: '->',
|
33 | devSuffix: '-dev'
|
34 | };
|
35 |
|
36 | var projectRoot = path.join(__dirname, '..');
|
37 | process.chdir(projectRoot);
|
38 |
|
39 | function main() {
|
40 | parseArgs();
|
41 | config = mergeArgsToDefaults(config);
|
42 |
|
43 | if (config.dryRun) status('Dry run\n');
|
44 |
|
45 | var newVersion = bumpVersion(config.files, config.bumpType);
|
46 |
|
47 | _gitBranchName()
|
48 | .then(function(stdout) {
|
49 | if (stdout.trim().toLowerCase() !== 'master') {
|
50 | throw new Error('You should be in master branch before running the script!');
|
51 | }
|
52 |
|
53 | return gitAdd([config.readmeFile]);
|
54 | })
|
55 | .then(function() {
|
56 | return gitAdd(config.files);
|
57 | })
|
58 | .then(function() {
|
59 | var message = Mustache.render(config.releaseMessage, {
|
60 | version: newVersion
|
61 | });
|
62 |
|
63 | return gitCommit(message);
|
64 | })
|
65 | .then(function() {
|
66 | return gitTag(newVersion);
|
67 | })
|
68 | .then(function() {
|
69 | return gitPushTag(newVersion);
|
70 | })
|
71 | .then(npmPublish)
|
72 | .then(function() {
|
73 | bumpVersion(config.files, 'dev');
|
74 | return gitAdd(config.files.concat(config.readmeFile));
|
75 | })
|
76 | .then(function() {
|
77 | return gitCommit(config.backToDevMessage);
|
78 | })
|
79 | .then(function() {
|
80 | gitPush();
|
81 | })
|
82 | .then(function() {
|
83 | console.log('');
|
84 | status('Release successfully done!');
|
85 | })
|
86 | .catch(function(err) {
|
87 | console.error('\n!! Releasing failed');
|
88 | console.trace(err);
|
89 | process.exit(2);
|
90 | });
|
91 | }
|
92 |
|
93 | function parseArgs() {
|
94 | program
|
95 | .usage('bump');
|
96 |
|
97 | program.on('--help', function() {
|
98 | console.log(' Example usage:');
|
99 | console.log('');
|
100 | console.log(' $ ./release.js minor');
|
101 | });
|
102 |
|
103 | program.parse(process.argv);
|
104 | }
|
105 |
|
106 | function mergeArgsToDefaults(config) {
|
107 | if (program.args[0]) {
|
108 | config.bumpType = program.args[0];
|
109 |
|
110 | if (!_.contains(['major', 'minor', 'patch'], config.bumpType)) {
|
111 | console.error('Error:', config.bumpType, 'is not a valid bump type');
|
112 | process.exit(1);
|
113 | }
|
114 | }
|
115 |
|
116 | return config;
|
117 | }
|
118 |
|
119 | function status( /* arguments */ ) {
|
120 | var args = Array.prototype.slice.call(arguments);
|
121 | console.log(config.consolePrefix, args.join(' '));
|
122 | }
|
123 |
|
124 | function run(cmd, msg) {
|
125 |
|
126 |
|
127 | return new Promise(function(resolve, reject) {
|
128 | if (msg) {
|
129 | status(msg);
|
130 | }
|
131 |
|
132 | if (config.dryRun) {
|
133 | return resolve();
|
134 | }
|
135 |
|
136 | var exec = shell.exec(cmd);
|
137 | var success = exec.code === 0;
|
138 |
|
139 | if (success) {
|
140 | resolve(exec.output);
|
141 | } else {
|
142 | var errMsg = 'Error executing: `' + cmd + '`\nOutput:\n' + exec.output;
|
143 | var err = new Error(errMsg);
|
144 | reject(err);
|
145 | }
|
146 | });
|
147 | }
|
148 |
|
149 |
|
150 |
|
151 |
|
152 |
|
153 |
|
154 |
|
155 | function bumpVersion(files, bumpType) {
|
156 | status('Bump', bumpType, 'version to files:', files.join(' '));
|
157 | if (config.dryRun) return '[not available in dry run]';
|
158 |
|
159 | var newVersion;
|
160 | var originalVersion;
|
161 | files.forEach(function(fileName) {
|
162 | var filePath = path.join(projectRoot, fileName);
|
163 |
|
164 | var data = JSON.parse(fs.readFileSync(filePath));
|
165 | originalVersion = data.version;
|
166 | var currentVersion = data.version;
|
167 | if (!semver.valid(currentVersion)) {
|
168 | var msg = 'Invalid version ' + currentVersion +
|
169 | ' in file ' + fileName;
|
170 | var err = new Error(msg);
|
171 | throw err;
|
172 | }
|
173 |
|
174 | if (S(currentVersion).endsWith(config.devSuffix)) {
|
175 | currentVersion = S(currentVersion).chompRight(config.devSuffix).s;
|
176 | }
|
177 |
|
178 | if (bumpType === 'dev') {
|
179 | newVersion = currentVersion + config.devSuffix;
|
180 | } else {
|
181 | newVersion = semver.inc(currentVersion, bumpType);
|
182 | }
|
183 | data.version = newVersion;
|
184 |
|
185 | var content = JSON.stringify(data, null, config.indentation);
|
186 | fs.writeFileSync(filePath, content);
|
187 |
|
188 | status('Bump', originalVersion, '->', newVersion, 'in',
|
189 | fileName);
|
190 | });
|
191 |
|
192 | bumpReadmeVersion(originalVersion, newVersion, bumpType);
|
193 |
|
194 | return newVersion;
|
195 | }
|
196 |
|
197 | function bumpReadmeVersion(oldVersion, newVersion, bumpType) {
|
198 | if (bumpType === 'dev') {
|
199 |
|
200 | return;
|
201 | }
|
202 |
|
203 | var oldReleaseVersion = oldVersion;
|
204 | if (S(oldReleaseVersion).endsWith(config.devSuffix)) {
|
205 | oldReleaseVersion = S(oldReleaseVersion).chompRight(config.devSuffix).s;
|
206 | }
|
207 |
|
208 | status('Replace readme version', oldReleaseVersion, '->', newVersion);
|
209 | if (config.dryRun) return;
|
210 |
|
211 | var filePath = path.join(projectRoot, config.readmeFile);
|
212 | var content = fs.readFileSync(filePath, {encoding: 'utf-8'});
|
213 |
|
214 |
|
215 | var re = new RegExp('Version: ' + oldReleaseVersion, 'g');
|
216 | var newContent = content.replace(re, 'Version: ' + newVersion);
|
217 |
|
218 |
|
219 | re = new RegExp('tree/[0-9]\\.[0-9]\\.[0-9]');
|
220 | newContent = newContent.replace(re, 'tree/' + oldReleaseVersion);
|
221 |
|
222 | fs.writeFileSync(filePath, newContent);
|
223 | }
|
224 |
|
225 | function gitAdd(files) {
|
226 | var cmd = 'git add ' + files.join(' ');
|
227 | var msg = 'Staged ' + files.length + ' files';
|
228 | return run(cmd, msg);
|
229 | }
|
230 |
|
231 | function gitCommit(message) {
|
232 | var cmd = 'git commit -m "' + message + '"';
|
233 | var msg = 'Commit files';
|
234 | return run(cmd, msg);
|
235 | }
|
236 |
|
237 | function gitTag(name) {
|
238 | var cmd = 'git tag ' + name;
|
239 | var msg = 'Created a new git tag: ' + name;
|
240 | return run(cmd, msg);
|
241 | }
|
242 |
|
243 | function gitPush() {
|
244 | if (config.noPush) return;
|
245 |
|
246 | var cmd = 'git push';
|
247 | var msg = 'Push to remote';
|
248 | return run(cmd, msg);
|
249 | }
|
250 |
|
251 | function gitPushTag(tagName) {
|
252 | if (config.noPush) return;
|
253 |
|
254 | var cmd = 'git push origin ' + tagName;
|
255 | var msg = 'Push created git tag to remote';
|
256 | return run(cmd, msg);
|
257 | }
|
258 |
|
259 | function npmPublish() {
|
260 | if (config.noPush) return;
|
261 |
|
262 | var cmd = 'npm publish';
|
263 | var msg = 'Publish to npm';
|
264 | return run(cmd, msg);
|
265 | }
|
266 |
|
267 | function _gitBranchName() {
|
268 | var cmd = 'git rev-parse --abbrev-ref HEAD';
|
269 | return run(cmd, false);
|
270 | }
|
271 |
|
272 |
|
273 | main();
|