From 827b5141df49a83bc4a2edc243343d5987901131 Mon Sep 17 00:00:00 2001 From: Timmy Willison Date: Fri, 20 Dec 2013 17:13:48 -0500 Subject: [PATCH] Release script: jquery-release integration Conflicts: build/release.js --- build/release.js | 532 +++++++++++++---------------------------------- package.json | 1 + 2 files changed, 148 insertions(+), 385 deletions(-) diff --git a/build/release.js b/build/release.js index be9f425cf..8624fbbd3 100644 --- a/build/release.js +++ b/build/release.js @@ -1,407 +1,169 @@ -#!/usr/bin/env node -/* - * jQuery Core Release Management - */ - -// Debugging variables -var debug = false, - skipRemote = false; - -var fs = require("fs"), - child = require("child_process"), - path = require("path"), - archiver = require("archiver"); - -var releaseVersion, - nextVersion, - isBeta, - pkg, - branch, - - scpURL = "jqadmin@code.origin.jquery.com:/var/www/html/code.jquery.com/", - cdnURL = "http://code.origin.jquery.com/", - repoURL = "git@github.com:jquery/jquery.git", - - // Windows needs the .cmd version but will find the non-.cmd - // On Windows, ensure the HOME environment variable is set - gruntCmd = process.platform === "win32" ? "grunt.cmd" : "grunt", - - devFile = "dist/jquery.js", - minFile = "dist/jquery.min.js", - mapFile = "dist/jquery.min.map", - - releaseFiles = { - "jquery-VER.js": devFile, - "jquery-VER.min.js": minFile, - "jquery-VER.min.map": mapFile, - "jquery.js": devFile, - "jquery.min.js": minFile, - "jquery.min.map": mapFile, - "jquery-latest.js": devFile, - "jquery-latest.min.js": minFile, - "jquery-latest.min.map": mapFile - }, - - jQueryFilesCDN = [], - - googleFilesCDN = [ - "jquery.js", "jquery.min.js", "jquery.min.map" - ], - - msFilesCDN = [ - "jquery-VER.js", "jquery-VER.min.js", "jquery-VER.min.map" - ]; - - -steps( - initialize, - checkGitStatus, - setReleaseVersion, - gruntBuild, - makeReleaseCopies, - copyTojQueryCDN, - buildGoogleCDN, - buildMicrosoftCDN, - createTag, - setNextVersion, - pushToGithub, - // publishToNpm, - exit -); - -function initialize( next ) { - - if ( process.argv[2] === "-d" ) { - process.argv.shift(); - debug = true; - console.warn("=== DEBUG MODE ===" ); +module.exports = function( Release ) { + + var + fs = require( "fs" ), + shell = require( "shelljs" ), + + // Windows needs the .cmd version but will find the non-.cmd + // On Windows, ensure the HOME environment variable is set + gruntCmd = process.platform === "win32" ? "grunt.cmd" : "grunt", + + devFile = "dist/jquery.js", + minFile = "dist/jquery.min.js", + mapFile = "dist/jquery.min.map", + + cdnFolder = "dist/cdn", + + releaseFiles = { + "jquery-VER.js": devFile, + "jquery-VER.min.js": minFile, + "jquery-VER.min.map": mapFile, + "jquery.js": devFile, + "jquery.min.js": minFile, + "jquery.min.map": mapFile, + "jquery-latest.js": devFile, + "jquery-latest.min.js": minFile, + "jquery-latest.min.map": mapFile + }, + + googleFilesCDN = [ + "jquery.js", "jquery.min.js", "jquery.min.map" + ], + + msFilesCDN = [ + "jquery-VER.js", "jquery-VER.min.js", "jquery-VER.min.map" + ], + + _complete = Release.complete; + + /** + * Generates copies for the CDNs + */ + function makeReleaseCopies() { + shell.mkdir( "-p", cdnFolder ); + + Object.keys( releaseFiles ).forEach(function( key ) { + var text, + builtFile = releaseFiles[ key ], + unpathedFile = key.replace( /VER/g, Release.newVersion ), + releaseFile = cdnFolder + "/" + unpathedFile; + + // Beta releases don't update the jquery-latest etc. copies + if ( !Release.preRelease || key.indexOf( "VER" ) >= 0 ) { + + if ( /\.map$/.test( releaseFile ) ) { + // Map files need to reference the new uncompressed name; + // assume that all files reside in the same directory. + // "file":"jquery.min.js","sources":["jquery.js"] + text = fs.readFileSync( builtFile, "utf8" ) + .replace( /"file":"([^"]+)","sources":\["([^"]+)"\]/, + "\"file\":\"" + unpathedFile.replace( /\.min\.map/, ".min.js" ) + + "\",\"sources\":[\"" + unpathedFile.replace( /\.min\.map/, ".js" ) + "\"]" ); + fs.writeFileSync( releaseFile, text ); + } else if ( /\.min\.js$/.test( releaseFile ) ) { + // Remove the source map comment; it causes way too many problems. + // Keep the map file in case DevTools allow manual association. + text = fs.readFileSync( builtFile, "utf8" ) + .replace( /\/\/# sourceMappingURL=\S+/, "" ); + fs.writeFileSync( releaseFile, text ); + } else if ( builtFile !== releaseFile ) { + shell.cp( "-f", builtFile, releaseFile ); + } + } + }); } - // First arg should be the version number being released - var newver, oldver, - exists = fs.existsSync || path.existsSync, - rsemver = /^(\d+)\.(\d+)\.(\d+)(?:-([\dA-Za-z\-]+(?:\.[\dA-Za-z\-]+)*))?$/, - version = ( process.argv[3] || "" ).toLowerCase().match( rsemver ) || {}, - major = version[1], - minor = version[2], - patch = version[3], - xbeta = version[4]; - - branch = process.argv[2]; - releaseVersion = process.argv[3]; - isBeta = !!xbeta; - - if ( !branch || !major || !minor || !patch ) { - die( "Usage: " + process.argv[1] + " branch releaseVersion" ); - } - if ( xbeta === "pre" ) { - die( "Cannot release a 'pre' version!" ); - } - if ( !exists( "package.json" ) ) { - die( "No package.json in this directory" ); + function buildGoogleCDN( next ) { + makeArchive( "googlecdn", googleFilesCDN, next ); } - pkg = readJSON( "package.json" ); - console.log( "Current version is " + pkg.version + "; generating release " + releaseVersion ); - version = pkg.version.match( rsemver ); - oldver = ( +version[1] ) * 10000 + ( +version[2] * 100 ) + ( +version[3] ); - newver = ( +major ) * 10000 + ( +minor * 100 ) + ( +patch ); - if ( newver < oldver ) { - die( "Next version is older than current version!" ); + function buildMicrosoftCDN( next ) { + makeArchive( "mscdn", msFilesCDN, next ); } - nextVersion = major + "." + minor + "." + ( isBeta ? patch : +patch + 1 ) + "-pre"; - next(); -} - -function checkGitStatus( next ) { - git( [ "status" ], function( error, stdout ) { - var onBranch = ((stdout||"").match( /On branch (\S+)/ ) || [])[1]; - if ( onBranch !== branch ) { - dieIfReal( "Branches don't match: Wanted " + branch + ", got " + onBranch ); - } - if ( /Changes to be committed/i.test( stdout ) ) { - dieIfReal( "Please commit changed files before attempting to push a release." ); + function makeArchive( cdn, files, next ) { + if ( Release.preRelease ) { + console.log( "Skipping archive creation for " + cdn + "; this is a beta release." ); + return; } - if ( /Changes not staged for commit/i.test( stdout ) ) { - dieIfReal( "Please stash files before attempting to push a release." ); - } - next(); - }); -} - -function setReleaseVersion( next ) { - updateVersion( releaseVersion ); - git( [ "commit", "-a", "-m", "Updating version to " + releaseVersion + "." ], next, debug ); -} - -function gruntBuild( next ) { - // First clean the dist directory of anything we're not about to rebuild - git( [ "clean", "-dfx", "dist/" ], function() { - exec( gruntCmd, [], function( error, stdout, stderr ) { - if ( error ) { - die( error + stderr ); - } - console.log( stdout ); - next(); - }, false ); - }, debug ); -} - -function makeReleaseCopies( next ) { - Object.keys( releaseFiles ).forEach(function( key ) { - var text, - builtFile = releaseFiles[ key ], - unpathedFile = key.replace( /VER/g, releaseVersion ), - releaseFile = "dist/" + unpathedFile; - // Beta releases don't update the jquery-latest etc. copies - if ( !isBeta || key.indexOf( "VER" ) >= 0 ) { + console.log( "Creating production archive for " + cdn ); - if ( /\.map$/.test( releaseFile ) ) { - // Map files need to reference the new uncompressed name; - // assume that all files reside in the same directory. - // "file":"jquery.min.js","sources":["jquery.js"] - text = fs.readFileSync( builtFile, "utf8" ) - .replace( /"file":"([^"]+)","sources":\["([^"]+)"\]/, - "\"file\":\"" + unpathedFile.replace( /\.min\.map/, ".min.js" ) + - "\",\"sources\":[\"" + unpathedFile.replace( /\.min\.map/, ".js" ) + "\"]" ); - fs.writeFileSync( releaseFile, text ); - } else if ( /\.min\.js$/.test( releaseFile ) ) { - // Remove the source map comment; it causes way too many problems. - // Keep the map file in case DevTools allow manual association. - text = fs.readFileSync( builtFile, "utf8" ) - .replace( /\/\/# sourceMappingURL=\S+/, "" ); - fs.writeFileSync( releaseFile, text ); - } else if ( builtFile !== releaseFile ) { - copy( builtFile, releaseFile ); - } + var archiver = require( "archiver" ), + md5file = cdnFolder + "/" + cdn + "-md5.txt", + output = fs.createWriteStream( cdnFolder + "/" + cdn + "-jquery-" + Release.newVersion + ".zip" ); - jQueryFilesCDN.push( releaseFile ); - } - }); - next(); -} - -function copyTojQueryCDN( next ) { - var cmds = []; - - jQueryFilesCDN.forEach(function( name ) { - cmds.push(function( nxt ){ - exec( "scp", [ name, scpURL ], nxt, debug || skipRemote ); + output.on( "error", function( err ) { + throw err; }); - cmds.push(function( nxt ){ - exec( "curl", [ cdnURL + name + "?reload" ], nxt, debug || skipRemote ); - }); - }); - cmds.push( next ); - steps.apply( this, cmds ); -} + output.on( "close", next ); + archiver.pipe( output ); -function buildGoogleCDN( next ) { - makeArchive( "googlecdn", googleFilesCDN, next ); -} - -function buildMicrosoftCDN( next ) { - makeArchive( "mscdn", msFilesCDN, next ); -} - -function createTag( next ) { - steps( - checkoutCommit, - commitDistFiles, - tagRelease, - checkoutBranch, - next - ); -} - -function setNextVersion( next ) { - updateVersion( nextVersion ); - git( [ "commit", "-a", "-m", "Updating the source version to " + nextVersion + "✓™" ], next, debug ); -} - -function pushToGithub( next ) { - git( [ "push", "--tags", repoURL, branch ], next, debug || skipRemote ); -} - -/* Utilities ----------------------------------------------------------------------- */ -function steps() { - var cur = 0, - st = arguments; - (function next(){ - process.nextTick(function(){ - st[ cur++ ]( next ); + files = files.map(function( item ) { + return cdnFolder + "/" + item.replace( /VER/g, Release.newVersion ); }); - })(); -} -function readJSON( filename ) { - return JSON.parse( fs.readFileSync( filename ) ); -} + shell.exec( "md5sum", files, function( code, stdout ) { + fs.writeFileSync( md5file, stdout ); + files.push( md5file ); -function replaceVersionJSON( file, version ) { - var text = fs.readFileSync( file, "utf8" ); - text = text.replace( /("version":\s*")[^"]+/, "$1" + version ); - fs.writeFileSync( file, text ); -} + files.forEach(function( file ) { + archiver.append( fs.createReadStream( file ), { name: file } ); + }); -function updateVersion( ver ) { - console.log( "Updating version to " + ver ); - pkg.version = ver; - if ( !debug ) { - [ "package.json", "bower.json" ].forEach(function( filename ) { - replaceVersionJSON( filename, ver ); + archiver.finalize(); }); } -} -function copy( oldFile, newFile, skip ) { - console.log( "Copying " + oldFile + " to " + newFile ); - if ( !skip ) { - fs.writeFileSync( newFile, fs.readFileSync( oldFile, "utf8" ) ); - } -} - -function exec( cmd, args, fn, skip ) { - if ( skip ) { - console.log( "# " + cmd + " " + args.join(" ") ); - fn( "", "", "" ); - } else { - console.log( cmd + " " + args.join(" ") ); - child.execFile( cmd, args, { env: process.env }, - function( err, stdout, stderr ) { - if ( err ) { - die( stderr || stdout || err ); - } - fn.apply( this, arguments ); + Release.define({ + issueTracker: "trac", + contributorReportId: 508, + /** + * Generates any release artifacts that should be included in the release. + * The callback must be invoked with an array of files that should be + * committed before creating the tag. + * @param {Function} callback + */ + generateArtifacts: function( callback ) { + if ( Release.exec( gruntCmd ).code !== 0 ) { + Release.abort("Grunt command failed"); } - ); - } -} - -function git( args, fn, skip ) { - exec( "git", args, fn, skip ); -} - - -/* Tag creation ----------------------------------------------------------------------- */ -function checkoutCommit( next ) { - git( [ "checkout", "HEAD^0" ], next, debug ); -} - -function commitDistFiles( next ) { - // Remove scripts property from package.json - // Building and bower are irrelevant as those files will be committed - // Makes for a clean npm install - var pkgClone = readJSON( "package.json" ); - delete pkgClone.scripts; - fs.writeFileSync( "package.json", JSON.stringify( pkgClone, null, "\t" ) ); - // Add files to be committed - git( [ "add", "package.json" ], function() { - git( [ "commit", "-m", "Remove scripts property from package.json" ], function() { - // Add jquery files for distribution in a final commit - git( [ "add", "-f", devFile, minFile, mapFile ], function() { - git( [ "commit", "-m", releaseVersion ], next, debug ); - }, debug ); - }, debug ); - }, debug ); -} - -function tagRelease( next ) { - git( [ "tag", releaseVersion ], next, debug ); -} - -function checkoutBranch( next ) { - // Reset files to previous state before leaving the commit - git( [ "reset", "--hard", "HEAD" ], function() { - git( [ "checkout", branch ], next, debug ); - }, debug ); -} - -/* Archive creation ----------------------------------------------------------------------- */ -function makeArchive( cdn, files, fn ) { - if ( isBeta ) { - console.log( "Skipping archive creation for " + cdn + "; " + releaseVersion + " is beta" ); - process.nextTick( fn ); - return; - } - - console.log( "Creating production archive for " + cdn ); - - var archive = archiver( "zip" ), - md5file = "dist/" + cdn + "-md5.txt", - output = fs.createWriteStream( "dist/" + cdn + "-jquery-" + releaseVersion + ".zip" ); - - archive.on( "error", function( err ) { - throw err; - }); - - output.on( "close", fn ); - archive.pipe( output ); - - files = files.map(function( item ) { - return "dist/" + item.replace( /VER/g, releaseVersion ); - }); - - exec( "md5sum", files, function( err, stdout ) { - fs.writeFileSync( md5file, stdout ); - files.push( md5file ); - - files.forEach(function( file ) { - archive.append( fs.createReadStream( file ), { name: file } ); - }); - - archive.finalize(); - }, false ); -} - -/* NPM ----------------------------------------------------------------------- */ -/* -function publishToNpm( next ) { - // Only publish the master branch to NPM - // You must be the jquery npm user for this not to fail - // To check, run `npm whoami` - // Log in to the jquery user with `npm adduser` - if ( branch !== "master" ) { - next(); - return; - } - git( [ "checkout", releaseVersion ], function() { - // Only publish committed files - git( [ "clean", "-dfx" ], function() { - var args = [ "publish" ]; - // Tag the version as beta if necessary - if ( isBeta ) { - args.push( "--tag", "beta" ); + makeReleaseCopies(); + callback([ "dist/jquery.js", "dist/jquery.min.js", "dist/jquery.min.map" ]); + }, + /** + * Release completion + */ + complete: function() { + // Build CDN archives + Release._walk( buildGoogleCDN, buildMicrosoftCDN, _complete ); + }, + /** + * Our trac milestones are different than the new version + * @example + * + * // For Release.newVersion equal to 2.1.0 or 1.11.0 + * Release._tracMilestone(); + * // => 1.11/2.1 + * + * // For Release.newVersion equal to 2.1.1 or 1.11.1 + * Release._tracMilestone(); + * // => 1.11.1/2.1.1 + */ + _tracMilestone: function() { + var otherVersion, + m = Release.newVersion.split( "." ), + major = m[0] | 0, + minor = m[1] | 0, + patch = m[2] | 0 ? "." + m[2] : "", + version = major + "." + minor + patch; + if ( major === 1) { + otherVersion = "2." + ( minor - 10 ) + patch; + return version + "/" + otherVersion; } - exec( "npm", args, function() { - git( [ "checkout", branch ], next, debug ); - }, debug || skipRemote ); - }, debug ); - }, debug); -}*/ - -/* Death ----------------------------------------------------------------------- */ -function die( msg ) { - console.error( "ERROR: " + msg ); - process.exit( 1 ); -} - -function dieIfReal( msg ) { - if ( debug ) { - console.log ( "DIE: " + msg ); - } else { - die( msg ); - } -} - -function exit() { - process.exit( 0 ); -} + otherVersion = "1." + ( minor + 10 ) + patch; + return otherVersion + "/" + version; + } + }); +}; diff --git a/package.json b/package.json index 95c759cc6..7dc24fc6a 100644 --- a/package.json +++ b/package.json @@ -35,6 +35,7 @@ "testswarm": "1.1.0", "load-grunt-tasks": "0.2.0", "requirejs": "2.1.9", + "shelljs": "0.2.6", "grunt": "0.4.2", "grunt-cli": "0.1.11", -- 2.39.5