release.js 6.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246
  1. #!/usr/bin/env node
  2. /*
  3. * jQuery Core Release Management
  4. */
  5. // Debugging variables
  6. var debug = false,
  7. skipRemote = false;
  8. var fs = require("fs"),
  9. child = require("child_process"),
  10. path = require("path");
  11. var releaseVersion,
  12. nextVersion,
  13. CDNFiles,
  14. isBeta,
  15. pkg,
  16. branch,
  17. scpURL = "jqadmin@code.origin.jquery.com:/var/www/html/code.jquery.com/",
  18. cdnURL = "http://code.origin.jquery.com/",
  19. repoURL = "git@github.com:jquery/jquery.git",
  20. // Windows needs the .cmd version but will find the non-.cmd
  21. // On Windows, ensure the HOME environment variable is set
  22. gruntCmd = process.platform === "win32" ? "grunt.cmd" : "grunt",
  23. devFile = "dist/jquery.js",
  24. minFile = "dist/jquery.min.js",
  25. mapFile = "dist/jquery.min.map",
  26. releaseFiles = {
  27. "jquery-VER.js": devFile,
  28. "jquery-VER.min.js": minFile,
  29. "jquery-VER.min.map": mapFile,
  30. "jquery.js": devFile,
  31. "jquery.min.js": minFile,
  32. "jquery.min.map": mapFile,
  33. "jquery-latest.js": devFile,
  34. "jquery-latest.min.js": minFile,
  35. "jquery-latest.min.map": mapFile
  36. };
  37. steps(
  38. initialize,
  39. checkGitStatus,
  40. tagReleaseVersion,
  41. gruntBuild,
  42. makeReleaseCopies,
  43. setNextVersion,
  44. uploadToCDN,
  45. pushToGithub,
  46. exit
  47. );
  48. function initialize( next ) {
  49. if ( process.argv[2] === "-d" ) {
  50. process.argv.shift();
  51. debug = true;
  52. console.warn("=== DEBUG MODE ===" );
  53. }
  54. // First arg should be the version number being released
  55. var newver, oldver,
  56. rversion = /^(\d)\.(\d+)\.(\d)((?:a|b|rc)\d|pre)?$/,
  57. version = ( process.argv[3] || "" ).toLowerCase().match( rversion ) || {},
  58. major = version[1],
  59. minor = version[2],
  60. patch = version[3],
  61. xbeta = version[4];
  62. branch = process.argv[2];
  63. releaseVersion = process.argv[3];
  64. isBeta = !!xbeta;
  65. if ( !branch || !major || !minor || !patch ) {
  66. die( "Usage: " + process.argv[1] + " branch releaseVersion" );
  67. }
  68. if ( xbeta === "pre" ) {
  69. die( "Cannot release a 'pre' version!" );
  70. }
  71. if ( !(fs.existsSync || path.existsSync)( "package.json" ) ) {
  72. die( "No package.json in this directory" );
  73. }
  74. pkg = JSON.parse( fs.readFileSync( "package.json" ) );
  75. console.log( "Current version is " + pkg.version + "; generating release " + releaseVersion );
  76. version = pkg.version.match( rversion );
  77. oldver = ( +version[1] ) * 10000 + ( +version[2] * 100 ) + ( +version[3] )
  78. newver = ( +major ) * 10000 + ( +minor * 100 ) + ( +patch );
  79. if ( newver < oldver ) {
  80. die( "Next version is older than current version!" );
  81. }
  82. nextVersion = major + "." + minor + "." + ( isBeta ? patch : +patch + 1 ) + "pre";
  83. next();
  84. }
  85. function checkGitStatus( next ) {
  86. git( [ "status" ], function( error, stdout, stderr ) {
  87. var onBranch = ((stdout||"").match( /On branch (\S+)/ ) || [])[1];
  88. if ( onBranch !== branch ) {
  89. die( "Branches don't match: Wanted " + branch + ", got " + onBranch );
  90. }
  91. if ( /Changes to be committed/i.test( stdout ) ) {
  92. die( "Please commit changed files before attemping to push a release." );
  93. }
  94. if ( /Changes not staged for commit/i.test( stdout ) ) {
  95. die( "Please stash files before attempting to push a release." );
  96. }
  97. next();
  98. });
  99. }
  100. function tagReleaseVersion( next ) {
  101. updatePackageVersion( releaseVersion );
  102. git( [ "commit", "-a", "-m", "Tagging the " + releaseVersion + " release." ], function(){
  103. git( [ "tag", releaseVersion ], next, debug);
  104. }, debug);
  105. }
  106. function gruntBuild( next ) {
  107. exec( gruntCmd, [], function( error, stdout ) {
  108. if ( error ) {
  109. die( error + stderr );
  110. }
  111. console.log( stdout );
  112. next();
  113. }, debug);
  114. }
  115. function makeReleaseCopies( next ) {
  116. CDNFiles = {};
  117. Object.keys( releaseFiles ).forEach(function( key ) {
  118. var text,
  119. builtFile = releaseFiles[ key ],
  120. releaseFile = key.replace( /VER/g, releaseVersion );
  121. // Beta releases don't update the jquery-latest etc. copies
  122. if ( !isBeta || key !== releaseFile ) {
  123. if ( /\.map$/.test( releaseFile ) ) {
  124. // Map files need to reference the new uncompressed name;
  125. // assume that all files reside in the same directory.
  126. // "file":"jquery.min.js","sources":["jquery.js"]
  127. text = fs.readFileSync( builtFile, "utf8" )
  128. .replace( /"file":"([^"]+)","sources":\["([^"]+)"\]/,
  129. "\"file\":\"" + releaseFile.replace( /\.min\.map/, ".min.js" ) +
  130. "\",\"sources\":[\"" + releaseFile.replace( /\.min\.map/, ".js" ) + "\"]" );
  131. console.log( "Modifying map " + builtFile + " to " + releaseFile );
  132. if ( !debug ) {
  133. fs.writeFileSync( releaseFile, text );
  134. }
  135. } else {
  136. copy( builtFile, releaseFile );
  137. }
  138. CDNFiles[ releaseFile ] = builtFile;
  139. }
  140. });
  141. next();
  142. }
  143. function setNextVersion( next ) {
  144. updatePackageVersion( nextVersion );
  145. git( [ "commit", "-a", "-m", "Updating the source version to " + nextVersion ], next, debug );
  146. }
  147. function uploadToCDN( next ) {
  148. var cmds = [];
  149. Object.keys( CDNFiles ).forEach(function( name ) {
  150. cmds.push(function( nxt ){
  151. exec( "scp", [ name, scpURL ], nxt, debug || skipRemote );
  152. });
  153. cmds.push(function( nxt ){
  154. exec( "curl", [ cdnURL + name + "?reload" ], nxt, debug || skipRemote );
  155. });
  156. });
  157. cmds.push( next );
  158. steps.apply( this, cmds );
  159. }
  160. function pushToGithub( next ) {
  161. git( [ "push", "--tags", repoURL, branch ], next, debug || skipRemote );
  162. }
  163. //==============================
  164. function steps() {
  165. var cur = 0,
  166. steps = arguments;
  167. (function next(){
  168. process.nextTick(function(){
  169. steps[ cur++ ]( next );
  170. });
  171. })();
  172. }
  173. function updatePackageVersion( ver ) {
  174. console.log( "Updating package.json version to " + ver );
  175. pkg.version = ver;
  176. if ( !debug ) {
  177. fs.writeFileSync( "package.json", JSON.stringify( pkg, null, "\t" ) + "\n" );
  178. }
  179. }
  180. function copy( oldFile, newFile ) {
  181. console.log( "Copying " + oldFile + " to " + newFile );
  182. if ( !debug ) {
  183. fs.writeFileSync( newFile, fs.readFileSync( oldFile, "utf8" ) );
  184. }
  185. }
  186. function git( args, fn, skip ) {
  187. exec( "git", args, fn, skip );
  188. }
  189. function exec( cmd, args, fn, skip ) {
  190. if ( skip ) {
  191. console.log( "# " + cmd + " " + args.join(" ") );
  192. fn( "", "", "" );
  193. } else {
  194. console.log( cmd + " " + args.join(" ") );
  195. child.execFile( cmd, args, { env: process.env },
  196. function( err, stdout, stderr ) {
  197. if ( err ) {
  198. die( stderr || stdout || err );
  199. }
  200. fn.apply( this, arguments );
  201. }
  202. );
  203. }
  204. }
  205. function die( msg ) {
  206. console.error( "ERROR: " + msg );
  207. process.exit( 1 );
  208. }
  209. function exit() {
  210. process.exit( 0 );
  211. }