Grunt: Rename TestSwarm job name pattern for PR. Close gh-1042.
[jquery.git] / grunt.js
blobdad96b77d045f70524cd1617c150560c4369d92e
1 /**
2  * Resources
3  *
4  * https://gist.github.com/2489540
5  *
6  */
8 module.exports = function( grunt ) {
10         "use strict";
12         // readOptionalJSON
13         // by Ben Alman
14         // https://gist.github.com/2876125
15         function readOptionalJSON( filepath ) {
16                 var data = {};
17                 try {
18                         data = grunt.file.readJSON( filepath );
19                         grunt.verbose.write( "Reading " + filepath + "..." ).ok();
20                 } catch(e) {}
21                 return data;
22         }
24         var file = grunt.file,
25                 log = grunt.log,
26                 verbose = grunt.verbose,
27                 config = grunt.config,
28                 distpaths = [
29                         "dist/jquery.js",
30                         "dist/jquery.min.js"
31                 ];
33         grunt.initConfig({
34                 pkg: "<json:package.json>",
35                 dst: readOptionalJSON("dist/.destination.json"),
36                 meta: {
37                         banner: "/*! jQuery v<%= pkg.version %> jquery.com | jquery.org/license */"
38                 },
39                 compare_size: {
40                         files: distpaths
41                 },
42                 selector: {
43                         "src/selector.js": [
44                                 "src/sizzle-jquery.js",
45                                 "src/sizzle/sizzle.js"
46                         ]
47                 },
48                 build: {
49                         "dist/jquery.js": [
50                                 "src/intro.js",
51                                 "src/core.js",
52                                 "src/callbacks.js",
53                                 "src/deferred.js",
54                                 "src/support.js",
55                                 "src/data.js",
56                                 "src/queue.js",
57                                 "src/attributes.js",
58                                 "src/event.js",
59                                 "src/selector.js",
60                                 "src/traversing.js",
61                                 "src/manipulation.js",
63                                 { flag: "css", src: "src/css.js" },
64                                 "src/serialize.js",
65                                 { flag: "ajax", src: "src/ajax.js" },
66                                 { flag: "ajax/script", src: "src/ajax/script.js", needs: ["ajax"]  },
67                                 { flag: "ajax/jsonp", src: "src/ajax/jsonp.js", needs: [ "ajax", "ajax/script" ]  },
68                                 { flag: "ajax/xhr", src: "src/ajax/xhr.js", needs: ["ajax"]  },
69                                 { flag: "effects", src: "src/effects.js", needs: ["css"] },
70                                 { flag: "offset", src: "src/offset.js", needs: ["css"] },
71                                 { flag: "dimensions", src: "src/dimensions.js", needs: ["css"] },
72                                 { flag: "deprecated", src: "src/deprecated.js" },
74                                 "src/exports.js",
75                                 "src/outro.js"
76                         ]
77                 },
78                 min: {
79                         "dist/jquery.min.js": [ "<banner>", "dist/jquery.js" ]
80                 },
82                 lint: {
83                         dist: "dist/jquery.js",
84                         grunt: "grunt.js",
85                         tests: "test/unit/**/*.js"
86                 },
88                 jshint: (function() {
89                         function jshintrc( path ) {
90                                 return readOptionalJSON( (path || "") + ".jshintrc" ) || {};
91                         }
93                         return {
94                                 grunt: jshintrc(),
95                                 dist: jshintrc( "src/" ),
96                                 tests: jshintrc( "test/" )
97                         };
98                 })(),
100                 qunit: {
101                         files: "test/index.html"
102                 },
103                 watch: {
104                         files: [
105                                 "<config:lint.grunt>", "<config:lint.tests>",
106                                 "src/**/*.js"
107                         ],
108                         tasks: "dev"
109                 },
110                 uglify: {
111                         codegen: {
112                                 ascii_only: true
113                         }
114                 }
115         });
117         // Default grunt.
118         grunt.registerTask( "default", "update_submodules selector build:*:* lint min dist:* compare_size" );
120         // Short list as a high frequency watch task
121         grunt.registerTask( "dev", "selector build:*:* lint" );
123         // Load grunt tasks from NPM packages
124         grunt.loadNpmTasks( "grunt-compare-size" );
125         grunt.loadNpmTasks( "grunt-git-authors" );
126         grunt.loadNpmTasks( "grunt-update-submodules" );
128         grunt.registerTask( "testswarm", function( commit, configFile ) {
129                 var jobName,
130                         testswarm = require( "testswarm" ),
131                         testUrls = [],
132                         pull = /PR-(\d+)/.exec( commit ),
133                         config = grunt.file.readJSON( configFile ).jquery,
134                         tests = "ajax attributes callbacks core css data deferred dimensions effects event manipulation offset queue serialize support traversing Sizzle".split(" ");
136                 if ( pull ) {
137                         jobName = "jQuery pull <a href='https://github.com/jquery/jquery/pull/" +
138                                 pull[ 1 ] + "'>#" + pull[ 1 ] + "</a>";
139                 } else {
140                         jobName = "jQuery commit #<a href='https://github.com/jquery/jquery/commit/" +
141                                 commit + "'>" + commit.substr( 0, 10 ) + "</a>";
142                 }
144                 tests.forEach(function( test ) {
145                         testUrls.push( config.testUrl + commit + "/test/index.html?module=" + test );
146                 });
148                 testswarm({
149                         url: config.swarmUrl,
150                         pollInterval: 10000,
151                         timeout: 1000 * 60 * 30,
152                         done: this.async()
153                 }, {
154                         authUsername: config.authUsername,
155                         authToken: config.authToken,
156                         jobName: jobName,
157                         runMax: config.runMax,
158                         "runNames[]": tests,
159                         "runUrls[]": testUrls,
160                         "browserSets[]": config.browserSets
161                 });
162         });
164         // Build src/selector.js
165         grunt.registerMultiTask( "selector", "Build src/selector.js", function() {
167                 var name = this.file.dest,
168                                 files = this.file.src,
169                                 sizzle = {
170                                         api: file.read( files[0] ),
171                                         src: file.read( files[1] )
172                                 },
173                                 compiled, parts;
175                 /**
177                         sizzle-jquery.js -> sizzle between "EXPOSE" blocks,
178                         replace define & window.Sizzle assignment
181                         // EXPOSE
182                         if ( typeof define === "function" && define.amd ) {
183                                 define(function() { return Sizzle; });
184                         } else {
185                                 window.Sizzle = Sizzle;
186                         }
187                         // EXPOSE
189                         Becomes...
191                         Sizzle.attr = jQuery.attr;
192                         jQuery.find = Sizzle;
193                         jQuery.expr = Sizzle.selectors;
194                         jQuery.expr[":"] = jQuery.expr.pseudos;
195                         jQuery.unique = Sizzle.uniqueSort;
196                         jQuery.text = Sizzle.getText;
197                         jQuery.isXMLDoc = Sizzle.isXML;
198                         jQuery.contains = Sizzle.contains;
200                  */
202                 // Break into 3 pieces
203                 parts = sizzle.src.split("// EXPOSE");
204                 // Replace the if/else block with api
205                 parts[1] = sizzle.api;
206                 // Rejoin the pieces
207                 compiled = parts.join("");
210                 verbose.write("Injected sizzle-jquery.js into sizzle.js");
212                 // Write concatenated source to file
213                 file.write( name, compiled );
215                 // Fail task if errors were logged.
216                 if ( this.errorCount ) {
217                         return false;
218                 }
220                 // Otherwise, print a success message.
221                 log.writeln( "File '" + name + "' created." );
222         });
225         // Special "alias" task to make custom build creation less grawlix-y
226         grunt.registerTask( "custom", function() {
227                 var done = this.async(),
228                                 args = [].slice.call(arguments),
229                                 modules = args.length ? args[0].replace(/,/g, ":") : "";
232                 // Translation example
233                 //
234                 //   grunt custom:+ajax,-dimensions,-effects,-offset
235                 //
236                 // Becomes:
237                 //
238                 //   grunt build:*:*:+ajax:-dimensions:-effects:-offset
240                 grunt.log.writeln( "Creating custom build...\n" );
242                 grunt.utils.spawn({
243                         cmd: process.platform === "win32" ? "grunt.cmd" : "grunt",
244                         args: [ "build:*:*:" + modules, "min" ]
245                 }, function( err, result ) {
246                         if ( err ) {
247                                 grunt.verbose.error();
248                                 done( err );
249                                 return;
250                         }
252                         grunt.log.writeln( result.replace("Done, without errors.", "") );
254                         done();
255                 });
256         });
258         // Special concat/build task to handle various jQuery build requirements
259         //
260         grunt.registerMultiTask(
261                 "build",
262                 "Concatenate source (include/exclude modules with +/- flags), embed date/version",
263                 function() {
264                         // Concat specified files.
265                         var compiled = "",
266                                 modules = this.flags,
267                                 optIn = !modules["*"],
268                                 explicit = optIn || Object.keys(modules).length > 1,
269                                 name = this.file.dest,
270                                 deps = {},
271                                 excluded = {},
272                                 version = config( "pkg.version" ),
273                                 excluder = function( flag, needsFlag ) {
274                                         // optIn defaults implicit behavior to weak exclusion
275                                         if ( optIn && !modules[ flag ] && !modules[ "+" + flag ] ) {
276                                                 excluded[ flag ] = false;
277                                         }
279                                         // explicit or inherited strong exclusion
280                                         if ( excluded[ needsFlag ] || modules[ "-" + flag ] ) {
281                                                 excluded[ flag ] = true;
283                                         // explicit inclusion overrides weak exclusion
284                                         } else if ( excluded[ needsFlag ] === false &&
285                                                 ( modules[ flag ] || modules[ "+" + flag ] ) ) {
287                                                 delete excluded[ needsFlag ];
289                                                 // ...all the way down
290                                                 if ( deps[ needsFlag ] ) {
291                                                         deps[ needsFlag ].forEach(function( subDep ) {
292                                                                 modules[ needsFlag ] = true;
293                                                                 excluder( needsFlag, subDep );
294                                                         });
295                                                 }
296                                         }
297                                 };
299                         // append commit id to version
300                         if ( process.env.COMMIT ) {
301                                 version += " " + process.env.COMMIT;
302                         }
304                         // figure out which files to exclude based on these rules in this order:
305                         //  dependency explicit exclude
306                         //  > explicit exclude
307                         //  > explicit include
308                         //  > dependency implicit exclude
309                         //  > implicit exclude
310                         // examples:
311                         //  *                  none (implicit exclude)
312                         //  *:*                all (implicit include)
313                         //  *:*:-css           all except css and dependents (explicit > implicit)
314                         //  *:*:-css:+effects  same (excludes effects because explicit include is trumped by explicit exclude of dependency)
315                         //  *:+effects         none except effects and its dependencies (explicit include trumps implicit exclude of dependency)
316                         this.file.src.forEach(function( filepath ) {
317                                 var flag = filepath.flag;
319                                 if ( flag ) {
321                                         excluder(flag);
323                                         // check for dependencies
324                                         if ( filepath.needs ) {
325                                                 deps[ flag ] = filepath.needs;
326                                                 filepath.needs.forEach(function( needsFlag ) {
327                                                         excluder( flag, needsFlag );
328                                                 });
329                                         }
330                                 }
331                         });
333                         // append excluded modules to version
334                         if ( Object.keys( excluded ).length ) {
335                                 version += " -" + Object.keys( excluded ).join( ",-" );
336                                 // set pkg.version to version with excludes, so minified file picks it up
337                                 grunt.config.set( "pkg.version", version );
338                         }
341                         // conditionally concatenate source
342                         this.file.src.forEach(function( filepath ) {
343                                 var flag = filepath.flag,
344                                                 specified = false,
345                                                 omit = false,
346                                                 message = "";
348                                 if ( flag ) {
349                                         if ( excluded[ flag ] !== undefined ) {
350                                                 message = ( "Excluding " + flag ).red;
351                                                 specified = true;
352                                                 omit = true;
353                                         } else {
354                                                 message = ( "Including " + flag ).green;
356                                                 // If this module was actually specified by the
357                                                 // builder, then st the flag to include it in the
358                                                 // output list
359                                                 if ( modules[ "+" + flag ] ) {
360                                                         specified = true;
361                                                 }
362                                         }
364                                         // Only display the inclusion/exclusion list when handling
365                                         // an explicit list.
366                                         //
367                                         // Additionally, only display modules that have been specified
368                                         // by the user
369                                         if ( explicit && specified ) {
370                                                 grunt.log.writetableln([ 27, 30 ], [
371                                                         message,
372                                                         ( "(" + filepath.src + ")").grey
373                                                 ]);
374                                         }
376                                         filepath = filepath.src;
377                                 }
379                                 if ( !omit ) {
380                                         compiled += file.read( filepath );
381                                 }
382                         });
384                         // Embed Version
385                         // Embed Date
386                         compiled = compiled.replace( /@VERSION/g, version )
387                                 .replace( "@DATE", function () {
388                                         var date = new Date();
390                                         // YYYY-MM-DD
391                                         return [
392                                                 date.getFullYear(),
393                                                 date.getMonth() + 1,
394                                                 date.getDate()
395                                         ].join( "-" );
396                                 });
398                         // Write concatenated source to file
399                         file.write( name, compiled );
401                         // Fail task if errors were logged.
402                         if ( this.errorCount ) {
403                                 return false;
404                         }
406                         // Otherwise, print a success message.
407                         log.writeln( "File '" + name + "' created." );
408                 });
410         // Allow custom dist file locations
411         grunt.registerTask( "dist", function() {
412                 var flags, paths, stored;
414                 // Check for stored destination paths
415                 // ( set in dist/.destination.json )
416                 stored = Object.keys( config("dst") );
418                 // Allow command line input as well
419                 flags = Object.keys( this.flags );
421                 // Combine all output target paths
422                 paths = [].concat( stored, flags ).filter(function( path ) {
423                         return path !== "*";
424                 });
426                 // Ensure the dist files are pure ASCII
427                 var fs = require("fs"),
428                         nonascii = false;
429                 distpaths.forEach(function( filename ) {
430                         var buf = fs.readFileSync( filename, "utf8" ),
431                         i, c;
432                         if ( buf.length !== Buffer.byteLength( buf, "utf8" ) ) {
433                                 log.writeln( filename + ": Non-ASCII characters detected:" );
434                                 for ( i = 0; i < buf.length; i++ ) {
435                                         c = buf.charCodeAt( i );
436                                         if ( c > 127 ) {
437                                                 log.writeln( "- position " + i + ": " + c );
438                                                 log.writeln( "-- " + buf.substring( i - 20, i + 20 ) );
439                                                 nonascii = true;
440                                         }
441                                 }
442                         }
443                 });
444                 if ( nonascii ) {
445                         return false;
446                 }
448                 // Proceed only if there are actual
449                 // paths to write to
450                 if ( paths.length ) {
452                         // 'distpaths' is declared at the top of the
453                         // module.exports function scope. It is an array
454                         // of default files that jQuery creates
455                         distpaths.forEach(function( filename ) {
456                                 paths.forEach(function( path ) {
457                                         var created;
459                                         if ( !/\/$/.test( path ) ) {
460                                                 path += "/";
461                                         }
463                                         created = path + filename.replace( "dist/", "" );
465                                         file.write( created, file.read(filename) );
467                                         log.writeln( "File '" + created + "' created." );
468                                 });
469                         });
470                 }
471         });