blob: 77fc16290b6d9d5ed181fa9a17c04da528e79970 [file] [log] [blame]
"use strict";
Error.stackTraceLimit = 100;
var astPasses = require("./ast_passes.js");
var node11 = parseInt(process.versions.node.split(".")[1], 10) >= 11;
var Q = require("q");
Q.longStackSupport = true;
module.exports = function( grunt ) {
var isCI = !!grunt.option("ci");
function getBrowsers() {
//Terse format to generate the verbose format required by sauce
var browsers = {
"internet explorer|WIN8": ["10"],
"internet explorer|WIN8.1": ["11"],
"firefox|Windows 7": ["3.5", "3.6", "4", "25"],
"firefox|WIN8.1": null,
"chrome|Windows 7": null,
"safari|Windows 7": ["5"],
"iphone|OS X 10.8": ["6.0"]
};
var ret = [];
for( var browserAndPlatform in browsers) {
var split = browserAndPlatform.split("|");
var browser = split[0];
var platform = split[1];
var versions = browsers[browserAndPlatform];
if( versions != null ) {
for( var i = 0, len = versions.length; i < len; ++i ) {
ret.push({
browserName: browser,
platform: platform,
version: versions[i]
});
}
}
else {
ret.push({
browserName: browser,
platform: platform
});
}
}
return ret;
}
var optionalModuleDependencyMap = {
"timers.js": ['Promise', 'INTERNAL'],
"any.js": ['Promise', 'Promise$_CreatePromiseArray', 'PromiseArray'],
"race.js": ['Promise', 'INTERNAL'],
"call_get.js": ['Promise'],
"filter.js": ['Promise', 'Promise$_CreatePromiseArray', 'PromiseArray', 'apiRejection'],
"generators.js": ['Promise', 'apiRejection', 'INTERNAL'],
"map.js": ['Promise', 'Promise$_CreatePromiseArray', 'PromiseArray', 'apiRejection'],
"nodeify.js": ['Promise'],
"promisify.js": ['Promise', 'INTERNAL'],
"props.js": ['Promise', 'PromiseArray'],
"reduce.js": ['Promise', 'Promise$_CreatePromiseArray', 'PromiseArray', 'apiRejection', 'INTERNAL'],
"settle.js": ['Promise', 'Promise$_CreatePromiseArray', 'PromiseArray'],
"some.js": ['Promise', 'Promise$_CreatePromiseArray', 'PromiseArray', 'apiRejection'],
"progress.js": ['Promise', 'isPromiseArrayProxy'],
"cancel.js": ['Promise', 'INTERNAL'],
"synchronous_inspection.js": ['Promise']
};
var optionalModuleRequireMap = {
"timers.js": true,
"race.js": true,
"any.js": true,
"call_get.js": true,
"filter.js": true,
"generators.js": true,
"map.js": true,
"nodeify.js": true,
"promisify.js": true,
"props.js": true,
"reduce.js": true,
"settle.js": true,
"some.js": true,
"progress.js": true,
"cancel.js": true,
"synchronous_inspection.js": true
};
function getOptionalRequireCode( srcs ) {
return srcs.reduce(function(ret, cur, i){
if( optionalModuleRequireMap[cur] ) {
ret += "require('./"+cur+"')("+ optionalModuleDependencyMap[cur] +");\n";
}
return ret;
}, "") + "\nPromise.prototype = Promise.prototype;\nreturn Promise;\n";
}
function getBrowserBuildHeader( sources ) {
var header = "/**\n * bluebird build version " + gruntConfig.pkg.version + "\n";
var enabledFeatures = ["core"];
var disabledFeatures = [];
featureLoop: for( var key in optionalModuleRequireMap ) {
for( var i = 0, len = sources.length; i < len; ++i ) {
var source = sources[i];
if( source.fileName === key ) {
enabledFeatures.push( key.replace( ".js", "") );
continue featureLoop;
}
}
disabledFeatures.push( key.replace( ".js", "") );
}
header += ( " * Features enabled: " + enabledFeatures.join(", ") + "\n" );
if( disabledFeatures.length ) {
header += " * Features disabled: " + disabledFeatures.join(", ") + "\n";
}
header += "*/\n";
return header;
}
function applyOptionalRequires( src, optionalRequireCode ) {
return src.replace( /};([^}]*)$/, optionalRequireCode + "\n};$1");
}
var CONSTANTS_FILE = './src/constants.js';
var BUILD_DEBUG_DEST = "./js/debug/bluebird.js";
var license;
function getLicense() {
if( !license ) {
var fs = require("fs");
var text = fs.readFileSync("LICENSE", "utf8");
text = text.split("\n").map(function(line, index){
return " * " + line;
}).join("\n")
license = "/**\n" + text + "\n */\n";
}
return license
}
var preserved;
function getLicensePreserve() {
if( !preserved ) {
var fs = require("fs");
var text = fs.readFileSync("LICENSE", "utf8");
text = text.split("\n").map(function(line, index){
if( index === 0 ) {
return " * @preserve " + line;
}
return " * " + line;
}).join("\n")
preserved = "/**\n" + text + "\n */\n";
}
return preserved;
}
function writeFile( dest, content ) {
grunt.file.write( dest, content );
grunt.log.writeln('File "' + dest + '" created.');
}
function writeFileAsync( dest, content ) {
var fs = require("fs");
return Q.nfcall(fs.writeFile, dest, content).then(function(){
grunt.log.writeln('File "' + dest + '" created.');
});
}
var gruntConfig = {};
var getGlobals = function() {
var fs = require("fs");
var file = "./src/constants.js";
var contents = fs.readFileSync(file, "utf8");
var rconstantname = /CONSTANT\(\s*([^,]+)/g;
var m;
var globals = {
Error: true,
args: true,
INLINE_SLICE: false,
TypeError: true,
RangeError: true,
__DEBUG__: false,
__BROWSER__: false,
process: true,
"console": false,
"require": false,
"module": false,
"define": false
};
while( ( m = rconstantname.exec( contents ) ) ) {
globals[m[1]] = false;
}
return globals;
}
gruntConfig.pkg = grunt.file.readJSON("package.json");
gruntConfig.jshint = {
all: {
options: {
globals: getGlobals(),
"bitwise": false,
"camelcase": true,
"curly": true,
"eqeqeq": true,
"es3": true,
"forin": true,
"immed": true,
"latedef": false,
"newcap": true,
"noarg": true,
"noempty": true,
"nonew": true,
"plusplus": false,
"quotmark": "double",
"undef": true,
"unused": true,
"strict": false,
"trailing": true,
"maxparams": 6,
"maxlen": 80,
"asi": false,
"boss": true,
"eqnull": true,
"evil": true,
"expr": false,
"funcscope": false,
"globalstrict": false,
"lastsemic": false,
"laxcomma": false,
"laxbreak": false,
"loopfunc": true,
"multistr": true,
"proto": false,
"scripturl": true,
"smarttabs": false,
"shadow": true,
"sub": true,
"supernew": false,
"validthis": true,
"browser": true,
"jquery": true,
"devel": true,
'-W014': true,
'-W116': true,
'-W106': true,
'-W064': true,
'-W097': true
},
files: {
src: [
"./src/finally.js",
"./src/direct_resolve.js",
"./src/synchronous_inspection.js",
"./src/thenables.js",
"./src/progress.js",
"./src/cancel.js",
"./src/any.js",
"./src/race.js",
"./src/call_get.js",
"./src/filter.js",
"./src/generators.js",
"./src/map.js",
"./src/nodeify.js",
"./src/promisify.js",
"./src/props.js",
"./src/reduce.js",
"./src/settle.js",
"./src/some.js",
"./src/util.js",
"./src/schedule.js",
"./src/queue.js",
"./src/errors.js",
"./src/captured_trace.js",
"./src/async.js",
"./src/catch_filter.js",
"./src/promise.js",
"./src/promise_array.js",
"./src/settled_promise_array.js",
"./src/some_promise_array.js",
"./src/properties_promise_array.js",
"./src/promise_inspection.js",
"./src/promise_resolver.js",
"./src/promise_spawn.js"
]
}
}
};
if( !isCI ) {
gruntConfig.jshint.all.options.reporter = require("jshint-stylish");
}
gruntConfig.connect = {
server: {
options: {
base: "./browser",
port: 9999
}
}
};
gruntConfig.watch = {};
gruntConfig["saucelabs-mocha"] = {
all: {
options: {
urls: ["http://127.0.0.1:9999/index.html"],
tunnelTimeout: 5,
build: process.env.TRAVIS_JOB_ID,
concurrency: 3,
browsers: getBrowsers(),
testname: "mocha tests",
tags: ["master"]
}
}
};
gruntConfig.bump = {
options: {
files: ['package.json'],
updateConfigs: [],
commit: true,
commitMessage: 'Release v%VERSION%',
commitFiles: ['-a'],
createTag: true,
tagName: 'v%VERSION%',
tagMessage: 'Version %VERSION%',
false: true,
pushTo: 'master',
gitDescribeOptions: '--tags --always --abbrev=1 --dirty=-d' // options to use with '$ git describe'
}
};
grunt.initConfig(gruntConfig);
grunt.loadNpmTasks("grunt-contrib-connect");
grunt.loadNpmTasks("grunt-saucelabs");
grunt.loadNpmTasks('grunt-contrib-jshint');
grunt.loadNpmTasks('grunt-contrib-watch');
grunt.loadNpmTasks('grunt-contrib-concat');
function runIndependentTest( file, cb , env) {
var fs = require("fs");
var path = require("path");
var sys = require('sys');
var spawn = require('child_process').spawn;
var p = path.join(process.cwd(), "test");
var stdio = [
'ignore',
grunt.option("verbose")
? process.stdout
: 'ignore',
process.stderr
];
var flags = node11 ? ["--harmony-generators"] : [];
flags.push("--allow-natives-syntax");
if( file.indexOf( "mocha/") > -1 || file === "aplus.js" ) {
var node = spawn(typeof node11 === "string" ? node11 : 'node',
flags.concat(["../mocharun.js", file]),
{cwd: p, stdio: stdio, env: env});
}
else {
var node = spawn('node', flags.concat(["./"+file]),
{cwd: p, stdio: stdio, env:env});
}
node.on('exit', exit );
function exit( code ) {
if( code !== 0 ) {
cb(new Error("process didn't exit normally. Code: " + code));
}
else {
cb(null);
}
}
}
function buildMain( sources, optionalRequireCode ) {
var fs = require("fs");
var Q = require("q");
var root = cleanDirectory("./js/main/");
return Q.all(sources.map(function( source ) {
var src = astPasses.removeAsserts( source.sourceCode, source.fileName );
src = astPasses.inlineExpansion( src, source.fileName );
src = astPasses.expandConstants( src, source.fileName );
src = src.replace( /__DEBUG__/g, "false" );
src = src.replace( /__BROWSER__/g, "false" );
if( source.fileName === "promise.js" ) {
src = applyOptionalRequires( src, optionalRequireCode );
}
var path = root + source.fileName;
return writeFileAsync(path, src);
}));
}
function buildDebug( sources, optionalRequireCode ) {
var fs = require("fs");
var Q = require("q");
var root = cleanDirectory("./js/debug/");
return Q.nfcall(require('mkdirp'), root).then(function(){
return Q.all(sources.map(function( source ) {
var src = astPasses.expandAsserts( source.sourceCode, source.fileName );
src = astPasses.inlineExpansion( src, source.fileName );
src = astPasses.expandConstants( src, source.fileName );
src = src.replace( /__DEBUG__/g, "true" );
src = src.replace( /__BROWSER__/g, "false" );
if( source.fileName === "promise.js" ) {
src = applyOptionalRequires( src, optionalRequireCode );
}
var path = root + source.fileName;
return writeFileAsync(path, src);
}));
});
}
function buildZalgo( sources, optionalRequireCode ) {
var fs = require("fs");
var Q = require("q");
var root = cleanDirectory("./js/zalgo/");
return Q.all(sources.map(function( source ) {
var src = astPasses.removeAsserts( source.sourceCode, source.fileName );
src = astPasses.inlineExpansion( src, source.fileName );
src = astPasses.expandConstants( src, source.fileName );
src = astPasses.asyncConvert( src, "async", "invoke", source.fileName);
src = src.replace( /__DEBUG__/g, "false" );
src = src.replace( /__BROWSER__/g, "false" );
if( source.fileName === "promise.js" ) {
src = applyOptionalRequires( src, optionalRequireCode );
}
var path = root + source.fileName;
return writeFileAsync(path, src);
}));
}
function buildBrowser( sources ) {
var fs = require("fs");
var browserify = require("browserify");
var b = browserify("./js/main/bluebird.js");
var dest = "./js/browser/bluebird.js";
var header = getBrowserBuildHeader( sources );
return Q.nbind(b.bundle, b)({
detectGlobals: false,
standalone: "Promise"
}).then(function(src) {
return writeFileAsync( dest,
getLicensePreserve() + src )
}).then(function() {
return Q.nfcall(fs.readFile, dest, "utf8" );
}).then(function( src ) {
src = header + src;
return Q.nfcall(fs.writeFile, dest, src );
});
}
function cleanDirectory(dir) {
if (isCI) return dir;
var fs = require("fs");
require("rimraf").sync(dir);
fs.mkdirSync(dir);
return dir;
}
function getOptionalPathsFromOption( opt ) {
opt = (opt + "").toLowerCase().split(/\s+/g);
return optionalPaths.filter(function(v){
v = v.replace("./src/", "").replace( ".js", "" ).toLowerCase();
return opt.indexOf(v) > -1;
});
}
var optionalPaths = [
"./src/timers.js",
"./src/synchronous_inspection.js",
"./src/any.js",
"./src/race.js",
"./src/call_get.js",
"./src/filter.js",
"./src/generators.js",
"./src/map.js",
"./src/nodeify.js",
"./src/promisify.js",
"./src/props.js",
"./src/reduce.js",
"./src/settle.js",
"./src/some.js",
"./src/progress.js",
"./src/cancel.js"
];
var mandatoryPaths = [
"./src/finally.js",
"./src/es5.js",
"./src/bluebird.js",
"./src/thenables.js",
"./src/assert.js",
"./src/global.js",
"./src/util.js",
"./src/schedule.js",
"./src/queue.js",
"./src/errors.js",
"./src/errors_api_rejection.js",
"./src/captured_trace.js",
"./src/async.js",
"./src/catch_filter.js",
"./src/promise.js",
"./src/promise_array.js",
"./src/settled_promise_array.js",
"./src/some_promise_array.js",
"./src/properties_promise_array.js",
"./src/promise_inspection.js",
"./src/promise_resolver.js",
"./src/promise_spawn.js",
"./src/direct_resolve.js"
];
function build( paths, isCI ) {
var fs = require("fs");
astPasses.readConstants(fs.readFileSync(CONSTANTS_FILE, "utf8"), CONSTANTS_FILE);
if( !paths ) {
paths = optionalPaths.concat(mandatoryPaths);
}
var optionalRequireCode = getOptionalRequireCode(paths.map(function(v) {
return v.replace("./src/", "");
}));
var Q = require("q");
var promises = [];
var sources = paths.map(function(v){
var promise = Q.nfcall(fs.readFile, v, "utf8");
promises.push(promise);
var ret = {};
ret.fileName = v.replace("./src/", "");
ret.sourceCode = promise.then(function(v){
ret.sourceCode = v;
});
return ret;
});
//Perform common AST passes on all builds
return Q.all(promises.slice()).then(function(){
sources.forEach( function( source ) {
var src = source.sourceCode
src = astPasses.removeComments(src, source.fileName);
src = getLicense() + src;
source.sourceCode = src;
});
if( isCI ) {
return buildDebug( sources, optionalRequireCode );
}
else {
return Q.all([
buildMain( sources, optionalRequireCode ).then( function() {
return buildBrowser( sources );
}),
buildDebug( sources, optionalRequireCode ),
buildZalgo( sources, optionalRequireCode )
]);
}
});
}
String.prototype.contains = function String$contains( str ) {
return this.indexOf( str ) >= 0;
};
function isSlowTest( file ) {
return file.contains("2.3.3") ||
file.contains("bind") ||
file.contains("unhandled_rejections");
}
function testRun( testOption ) {
var fs = require("fs");
var path = require("path");
var done = this.async();
var totalTests = 0;
var testsDone = 0;
function testDone() {
testsDone++;
if( testsDone >= totalTests ) {
done();
}
}
var files;
if( testOption === "aplus" ) {
files = fs.readdirSync("test/mocha").filter(function(f){
return /^\d+\.\d+\.\d+/.test(f);
}).map(function( f ){
return "mocha/" + f;
});
}
else {
files = testOption === "all"
? fs.readdirSync('test')
.concat(fs.readdirSync('test/mocha')
.map(function(fileName){
return "mocha/" + fileName
})
)
: [testOption + ".js" ];
if( testOption !== "all" &&
!fs.existsSync( "./test/" + files[0] ) ) {
files[0] = "mocha/" + files[0];
}
}
files = files.filter(function(fileName){
if( !node11 && fileName.indexOf("generator") > -1 ) {
return false;
}
return /\.js$/.test(fileName);
}).map(function(f){
return f.replace( /(\d)(\d)(\d)/, "$1.$2.$3" );
});
var slowTests = files.filter(isSlowTest);
files = files.filter(function(file){
return !isSlowTest(file);
});
function runFile(file) {
totalTests++;
grunt.log.writeln("Running test " + file );
var env = undefined;
if (file.indexOf("bluebird-debug-env-flag") >= 0) {
env = Object.create(process.env);
env["BLUEBIRD_DEBUG"] = true;
}
runIndependentTest(file, function(err) {
if( err ) throw new Error(err + " " + file + " failed");
grunt.log.writeln("Test " + file + " succeeded");
testDone();
if( files.length > 0 ) {
runFile( files.shift() );
}
}, env);
}
slowTests.forEach(runFile);
var maxParallelProcesses = 10;
var len = Math.min( files.length, maxParallelProcesses );
for( var i = 0; i < len; ++i ) {
runFile( files.shift() );
}
}
grunt.registerTask( "build", function() {
var done = this.async();
var features = grunt.option("features");
var paths = null;
if( features ) {
paths = getOptionalPathsFromOption( features ).concat( mandatoryPaths );
}
build( paths, isCI ).then(function() {
done();
}).catch(function(e) {
if( e.fileName && e.stack ) {
console.log(e.scriptSrc);
var stack = e.stack.split("\n");
stack[0] = stack[0] + " " + e.fileName;
console.error(stack.join("\n"));
if (!grunt.option("verbose")) {
console.error("use --verbose to see the source code");
}
}
else {
console.error(e.stack);
}
done(false);
});
});
grunt.registerTask( "testrun", function(){
var testOption = grunt.option("run");
var node11path = grunt.option("node11");
if (typeof node11path === "string" && node11path) {
node11 = node11path;
}
if( !testOption ) testOption = "all";
else {
testOption = ("" + testOption);
testOption = testOption
.replace( /\.js$/, "" )
.replace( /[^a-zA-Z0-9_-]/g, "" );
}
testRun.call( this, testOption );
});
grunt.registerTask( "test", ["jshint", "build", "testrun"] );
grunt.registerTask( "test-browser", ["connect", "saucelabs-mocha"]);
grunt.registerTask( "default", ["jshint", "build"] );
grunt.registerTask( "dev", ["connect", "watch"] );
};