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