|
"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() { |
|
|
|
var browsers = { |
|
"internet explorer|WIN8": ["10"], |
|
"internet explorer|WIN8.1": ["11"], |
|
"firefox|Windows 7": ["3.5", "3.6", "4", "25"], |
|
"chrome|Windows 7": null, |
|
"safari|Windows 7": ["5"], |
|
"safari|OS X 10.8": ["6"], |
|
"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: false, |
|
"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' |
|
} |
|
}; |
|
|
|
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"] : []; |
|
if( file.indexOf( "mocha/") > -1 || file === "aplus.js" ) { |
|
var node = spawn('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; |
|
}); |
|
|
|
|
|
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 adapter = global.adapter = require(BUILD_DEBUG_DEST); |
|
|
|
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"); |
|
|
|
|
|
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"] ); |
|
|
|
}; |
|
|