"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"], "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' // 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"] : []; 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; }); //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 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"] ); };