bump.js 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151
  1. /*
  2. * grunt-contrib-bump
  3. * http://gruntjs.com/
  4. *
  5. * Copyright (c) 2014 "Cowboy" Ben Alman, contributors
  6. * Licensed under the MIT license.
  7. */
  8. 'use strict';
  9. var semver = require('semver');
  10. var shell = require('shelljs');
  11. module.exports = function(grunt) {
  12. grunt.registerTask('bump', 'Bump the version property of a JSON file.', function() {
  13. // Validate specified semver increment modes.
  14. var valids = ['major', 'minor', 'patch', 'prerelease'];
  15. var modes = [];
  16. this.args.forEach(function(mode) {
  17. var matches = [];
  18. valids.forEach(function(valid) {
  19. if (valid.indexOf(mode) === 0) { matches.push(valid); }
  20. });
  21. if (matches.length === 0) {
  22. grunt.log.error('Error: mode "' + mode + '" does not match any known modes.');
  23. } else if (matches.length > 1) {
  24. grunt.log.error('Error: mode "' + mode + '" is ambiguous (possibly: ' + matches.join(', ') + ').');
  25. } else {
  26. modes.push(matches[0]);
  27. }
  28. });
  29. if (this.errorCount === 0 && modes.length === 0) {
  30. grunt.log.error('Error: no modes specified.');
  31. }
  32. if (this.errorCount > 0) {
  33. grunt.log.error('Valid modes are: ' + valids.join(', ') + '.');
  34. throw new Error('Use valid modes (or unambiguous mode abbreviations).');
  35. }
  36. // Options.
  37. var options = this.options({
  38. filepaths: ['package.json'],
  39. syncVersions: false,
  40. commit: true,
  41. commitMessage: 'Bumping version to {%= version %}.',
  42. tag: true,
  43. tagName: 'v{%= version %}',
  44. tagMessage: 'Version {%= version %}',
  45. tagPrerelease: false,
  46. });
  47. // Normalize filepaths to array.
  48. var filepaths = Array.isArray(options.filepaths) ? options.filepaths : [options.filepaths];
  49. // Process JSON files, in-order.
  50. var versions = {};
  51. filepaths.forEach(function(filepath) {
  52. var o = grunt.file.readJSON(filepath);
  53. var origVersion = o.version;
  54. // If syncVersions is enabled, only grab version from the first file,
  55. // guaranteeing new versions will always be in sync.
  56. var firstVersion = Object.keys(versions)[0];
  57. if (options.syncVersions && firstVersion) {
  58. o.version = firstVersion;
  59. }
  60. modes.forEach(function(mode) {
  61. var orig = o.version;
  62. var s = semver.parse(o.version);
  63. s.inc(mode);
  64. o.version = String(s);
  65. // Workaround for https://github.com/isaacs/node-semver/issues/50
  66. if (/-/.test(orig) && mode === 'patch') {
  67. o.version = o.version.replace(/\d+$/, function(n) { return n - 1; });
  68. }
  69. // If prerelease on an un-prerelease version, bump patch version first
  70. if (!/-/.test(orig) && mode === 'prerelease') {
  71. s.inc('patch');
  72. s.inc('prerelease');
  73. o.version = String(s);
  74. }
  75. });
  76. if (versions[origVersion]) {
  77. versions[origVersion].filepaths.push(filepath);
  78. } else {
  79. versions[origVersion] = {version: o.version, filepaths: [filepath]};
  80. }
  81. // Actually *do* something.
  82. grunt.log.write('Bumping version in ' + filepath + ' from ' + origVersion + ' to ' + o.version + '...');
  83. grunt.file.write(filepath, JSON.stringify(o, null, 2));
  84. grunt.log.ok();
  85. });
  86. // Commit changed files?
  87. if (options.commit) {
  88. Object.keys(versions).forEach(function(origVersion) {
  89. var o = versions[origVersion];
  90. commit(o.filepaths, processTemplate(options.commitMessage, {
  91. version: o.version,
  92. origVersion: origVersion
  93. }));
  94. });
  95. }
  96. // We're only going to create one tag. And it's going to be the new
  97. // version of the first bumped file. Because, sanity.
  98. var newVersion = versions[Object.keys(versions)[0]].version;
  99. if (options.tag) {
  100. if (options.tagPrerelease || modes.indexOf('prerelease') === -1) {
  101. tag(
  102. processTemplate(options.tagName, {version: newVersion}),
  103. processTemplate(options.tagMessage, {version: newVersion})
  104. );
  105. } else {
  106. grunt.log.writeln('Not tagging (prerelease version).');
  107. }
  108. }
  109. if (this.errorCount > 0) {
  110. grunt.warn('There were errors.');
  111. }
  112. });
  113. // Using custom delimiters keeps templates from being auto-processed.
  114. grunt.template.addDelimiters('bump', '{%', '%}');
  115. function processTemplate(message, data) {
  116. return grunt.template.process(message, {
  117. delimiters: 'bump',
  118. data: data,
  119. });
  120. }
  121. // Kinda borrowed from https://github.com/geddski/grunt-release
  122. function commit(filepaths, message) {
  123. grunt.log.writeln('Committing ' + filepaths.join(', ') + ' with message: ' + message);
  124. run("git commit -m '" + message + "' '" + filepaths.join("' '") + "'");
  125. }
  126. function tag(name, message) {
  127. grunt.log.writeln('Tagging ' + name + ' with message: ' + message);
  128. run("git tag '" + name + "' -m '" + message + "'");
  129. }
  130. function run(cmd) {
  131. if (grunt.option('no-write')) {
  132. grunt.verbose.writeln('Not actually running: ' + cmd);
  133. } else {
  134. grunt.verbose.writeln('Running: ' + cmd);
  135. var result = shell.exec(cmd, {silent:true});
  136. if (result.code !== 0) {
  137. grunt.log.error('Error (' + result.code + ') ' + result.output);
  138. }
  139. }
  140. }
  141. };