module.exports = function(grunt) { var pkg, s3, semver, version, verParts, uglify; semver = require('semver'); pkg = grunt.file.readJSON('package.json'); uglify = require('uglify-js'); try { s3 = grunt.file.readJSON('.s3config.json'); } catch(e) { s3 = {}; } verParts = pkg.version.split('.'); version = { full: pkg.version, major: verParts[0], minor: verParts[1], patch: verParts[2] }; version.majorMinor = version.major + '.' + version.minor; // loading predefined source order from source-loader.js // trust me, this is the easist way to do it so far /*jshint undef:false, evil:true */ var blockSourceLoading = true; var sourceFiles; // Needed to satisfy jshint eval(grunt.file.read('./build/source-loader.js')); // Project configuration. grunt.initConfig({ pkg: pkg, build: { src: 'src/js/dependencies.js', options: { baseDir: 'src/js/' } }, clean: { build: ['build/files/*'], dist: ['dist/*'] }, jshint: { src: { src: ['src/js/*.js', 'Gruntfile.js', 'test/unit/*.js'], options: { jshintrc: '.jshintrc' } } }, minify: { source:{ src: ['build/files/combined.video.js', 'build/compiler/goog.base.js', 'src/js/exports.js'], externs: ['src/js/player.externs.js', 'src/js/media/flash.externs.js'], dest: 'build/files/minified.video.js' }, tests: { src: ['build/files/combined.video.js', 'build/compiler/goog.base.js', 'src/js/exports.js', 'test/unit/*.js'], externs: ['src/js/player.externs.js', 'src/js/media/flash.externs.js', 'test/qunit-externs.js', 'test/sinon-externs.js'], dest: 'build/files/test.minified.video.js' } }, dist: {}, qunit: { source: ['test/index.html'], minified: ['test/minified.html'], minified_api: ['test/minified-api.html'] }, watch: { files: [ 'src/**/*', 'test/unit/*.js', 'Gruntfile.js' ], tasks: 'dev' }, connect: { dev: { options: { port: 9999, keepalive: true } } }, copy: { minor: { files: [ {expand: true, cwd: 'build/files/', src: ['*'], dest: 'dist/'+version.majorMinor+'/', filter: 'isFile'} // includes files in path ] }, patch: { files: [ {expand: true, cwd: 'build/files/', src: ['*'], dest: 'dist/'+version.full+'/', filter: 'isFile'} // includes files in path ] } }, s3: { options: s3, minor: { upload: [ { src: 'dist/cdn/*', dest: 'vjs/'+version.majorMinor+'/', rel: 'dist/cdn/', headers: { 'Cache-Control': 'public, max-age=2628000' } } ] }, patch: { upload: [ { src: 'dist/cdn/*', dest: 'vjs/'+version.full+'/', rel: 'dist/cdn/', headers: { 'Cache-Control': 'public, max-age=31536000' } } ] } }, cssmin: { minify: { expand: true, cwd: 'build/files/', src: ['video-js.css'], dest: 'build/files/', ext: '.min.css' } }, less: { dev: { files: { 'build/files/video-js.css': 'src/css/video-js.less' } } }, karma: { // this config file applies to all following configs except if overwritten options: { configFile: 'test/karma.conf.js' }, // this only runs on PRs from the mainrepo on saucelabs saucelabs: { browsers: ['chrome_sl'] }, // these are run locally on local browsers dev: { browsers: ['Chrome', 'Firefox', 'Safari'] }, chromecanary: { browsers: ['ChromeCanary'] }, chrome: { browsers: ['Chrome'] }, firefox: { browsers: ['Firefox'] }, safari: { browsers: ['Safari'] }, ie: { browsers: ['IE'] }, phantomjs: { browsers: ['PhantomJS'] }, // This is all the minified tests run locally on local browsers minified_dev: { browsers: ['Chrome', 'Firefox', 'Safari'], configFile: 'test/karma.minified.conf.js' }, minified_chromecanary: { browsers: ['ChromeCanary'], configFile: 'test/karma.minified.conf.js' }, minified_chrome: { browsers: ['Chrome'], configFile: 'test/karma.minified.conf.js' }, minified_firefox: { browsers: ['Firefox'], configFile: 'test/karma.minified.conf.js' }, minified_safari: { browsers: ['Safari'], configFile: 'test/karma.minified.conf.js' }, minified_ie: { browsers: ['IE'], configFile: 'test/karma.minified.conf.js' }, minified_phantomjs: { browsers: ['PhantomJS'], configFile: 'test/karma.minified.conf.js' }, // This is all the minified api tests run locally on local browsers minified_api_dev: { browsers: ['Chrome', 'Firefox', 'Safari'], configFile: 'test/karma.minified.api.conf.js' }, minified_api_chromecanary: { browsers: ['ChromeCanary'], configFile: 'test/karma.minified.api.conf.js' }, minified_api_chrome: { browsers: ['Chrome'], configFile: 'test/karma.minified.api.conf.js' }, minified_api_firefox: { browsers: ['Firefox'], configFile: 'test/karma.minified.api.conf.js' }, minified_api_safari: { browsers: ['Safari'], configFile: 'test/karma.minified.api.conf.js' }, minified_api_ie: { browsers: ['IE'], configFile: 'test/karma.minified.api.conf.js' }, minified_api_phantomjs: { browsers: ['PhantomJS'], configFile: 'test/karma.minified.api.conf.js' } }, vjsdocs: { all: { src: sourceFiles, dest: 'docs/api', options: { baseURL: 'https://github.com/videojs/video.js/blob/master/' } } }, zip: { dist: { router: function (filepath) { var path = require('path'); return path.relative('dist', filepath); }, // compression: 'DEFLATE', src: ['dist/video-js/**/*'], dest: 'dist/video-js-' + version.full + '.zip' } }, usebanner: { dist: { options: { position: 'top', banner: '/*! Video.js v' + version.full + ' <%= pkg.copyright %> */ ', linebreak: true }, files: { src: [ 'build/files/minified.video.js'] } } }, version: { options: { pkg: 'package.json' }, major: { options: { release: 'major' }, src: ['package.json', 'bower.json', 'component.json'] }, minor: { options: { release: 'minor' }, src: ['package.json', 'bower.json', 'component.json'] }, patch: { options: { release: 'patch' }, src: ['package.json', 'bower.json', 'component.json'] } }, tagrelease: { file: 'package.json', commit: true, message: 'Release %version%', prefix: 'v' } }); grunt.loadNpmTasks('grunt-contrib-connect'); grunt.loadNpmTasks('grunt-contrib-jshint'); grunt.loadNpmTasks('grunt-contrib-qunit'); grunt.loadNpmTasks('grunt-contrib-watch'); grunt.loadNpmTasks('grunt-contrib-clean'); grunt.loadNpmTasks('grunt-contrib-copy'); grunt.loadNpmTasks('grunt-contrib-cssmin'); grunt.loadNpmTasks('grunt-contrib-less'); grunt.loadNpmTasks('grunt-s3'); grunt.loadNpmTasks('contribflow'); grunt.loadNpmTasks('grunt-karma'); grunt.loadNpmTasks('videojs-doc-generator'); grunt.loadNpmTasks('grunt-zip'); grunt.loadNpmTasks('grunt-banner'); grunt.loadNpmTasks('grunt-version'); grunt.loadNpmTasks('grunt-tagrelease'); grunt.loadNpmTasks('chg'); // grunt.loadTasks('./docs/tasks/'); // grunt.loadTasks('../videojs-doc-generator/tasks/'); grunt.registerTask('pretask', ['jshint', 'less', 'build', 'minify', 'usebanner']); // Default task. grunt.registerTask('default', ['pretask', 'dist']); // Development watch task grunt.registerTask('dev', ['jshint', 'less', 'build', 'qunit:source']); grunt.registerTask('test-qunit', ['pretask', 'qunit']); // The test task will run `karma:saucelabs` when running in travis, // when running via a PR from a fork, it'll run qunit tests in phantom using // karma otherwise, it'll run the tests in chrome via karma // You can specify which browsers to build with by using grunt-style arguments // or separating them with a comma: // grunt test:chrome:firefox # grunt-style // grunt test:chrome,firefox # comma-separated grunt.registerTask('test', function() { var tasks = this.args, tasksMinified, tasksMinifiedApi; grunt.task.run(['pretask']); if (process.env.TRAVIS_PULL_REQUEST !== 'false') { grunt.task.run(['karma:phantomjs', 'karma:minified_phantomjs', 'karma:minified_api_phantomjs']); } else if (process.env.TRAVIS) { grunt.task.run(['karma:saucelabs']); } else { // if we aren't running this in a CI, but running it manually, we can // supply arguments to this task. These arguments are either colon (`:`) // separated which is the default grunt separator for arguments, or they // are comma (`,`) separated to make it easier. // The arguments are the names of which browsers you want. It'll then // make sure you have the `minified` and `minified_api` for those browsers // as well. if (tasks.length === 0) { tasks.push('chrome'); } if (tasks.length === 1) { tasks = tasks[0].split(','); } tasksMinified = tasks.slice(); tasksMinifiedApi = tasks.slice(); tasksMinified = tasksMinified.map(function(task) { return 'minified_' + task; }); tasksMinifiedApi = tasksMinifiedApi.map(function(task) { return 'minified_api_' + task; }); tasks = tasks.concat(tasksMinified).concat(tasksMinifiedApi); tasks = tasks.map(function(task) { return 'karma:' + task; }); grunt.task.run(tasks); } }); var fs = require('fs'), gzip = require('zlib').gzip; grunt.registerMultiTask('build', 'Building Source', function(){ // Fix windows file path delimiter issue var i = sourceFiles.length; while (i--) { sourceFiles[i] = sourceFiles[i].replace(/\\/g, '/'); } // Create a combined sources file. https://github.com/zencoder/video-js/issues/287 var combined = ''; sourceFiles.forEach(function(result){ combined += grunt.file.read(result); }); // Replace CDN version ref in js. Use major/minor version. combined = combined.replace(/GENERATED_CDN_VSN/g, version.majorMinor); grunt.file.write('build/files/combined.video.js', combined); // Copy over other files // grunt.file.copy('src/css/video-js.png', 'build/files/video-js.png'); grunt.file.copy('node_modules/videojs-swf/dist/video-js.swf', 'build/files/video-js.swf'); // Inject version number into css file var css = grunt.file.read('build/files/video-js.css'); css = css.replace(/GENERATED_AT_BUILD/g, version.full); grunt.file.write('build/files/video-js.css', css); // Copy over font files grunt.file.recurse('src/css/font', function(absdir, rootdir, subdir, filename) { // Block .DS_Store files if ('filename'.substring(0,1) !== '.') { grunt.file.copy(absdir, 'build/files/font/' + filename); } }); // Minify CSS grunt.task.run(['cssmin']); }); grunt.registerMultiTask('minify', 'Minify JS files using Closure Compiler.', function() { var done = this.async(); var exec = require('child_process').exec; var externs = this.data.externs || []; var dest = this.data.dest; var filePatterns = []; // Make sure deeper directories exist for compiler grunt.file.write(dest, ''); if (this.data.sourcelist) { filePatterns = filePatterns.concat(grunt.file.read(this.data.sourcelist).split(',')); } if (this.data.src) { filePatterns = filePatterns.concat(this.data.src); } // Build closure compiler shell command var command = 'java -jar build/compiler/compiler.jar' + ' --compilation_level ADVANCED_OPTIMIZATIONS' // + ' --formatting=pretty_print' + ' --js_output_file=' + dest + ' --create_source_map ' + dest + '.map --source_map_format=V3' + ' --jscomp_warning=checkTypes --warning_level=VERBOSE' + ' --output_wrapper "(function() {%output%})();"'; //@ sourceMappingURL=video.js.map // Add each js file grunt.file.expand(filePatterns).forEach(function(file){ command += ' --js='+file; }); // Add externs externs.forEach(function(extern){ command += ' --externs='+extern; }); // Run command exec(command, { maxBuffer: 500*1024 }, function(err, stdout, stderr){ if (err) { grunt.warn(err); done(false); } if (stdout) { grunt.log.writeln(stdout); } done(); }); }); grunt.registerTask('dist-copy', 'Assembling distribution', function(){ var css, jsmin, jsdev, cdnjs; // Manually copy each source file grunt.file.copy('build/files/minified.video.js', 'dist/video-js/video.js'); grunt.file.copy('build/files/combined.video.js', 'dist/video-js/video.dev.js'); grunt.file.copy('build/files/video-js.css', 'dist/video-js/video-js.css'); grunt.file.copy('build/files/video-js.min.css', 'dist/video-js/video-js.min.css'); grunt.file.copy('node_modules/videojs-swf/dist/video-js.swf', 'dist/video-js/video-js.swf'); grunt.file.copy('build/demo-files/demo.html', 'dist/video-js/demo.html'); grunt.file.copy('build/demo-files/demo.captions.vtt', 'dist/video-js/demo.captions.vtt'); grunt.file.copy('src/css/video-js.less', 'dist/video-js/video-js.less'); // Copy over font files grunt.file.recurse('build/files/font', function(absdir, rootdir, subdir, filename) { // Block .DS_Store files if ('filename'.substring(0,1) !== '.') { grunt.file.copy(absdir, 'dist/video-js/font/' + filename); } }); // ds_store files sometime find their way in if (grunt.file.exists('dist/video-js/.DS_Store')) { grunt.file['delete']('dist/video-js/.DS_Store'); } // CDN version uses already hosted font files // Minified version only, doesn't need demo files grunt.file.copy('build/files/minified.video.js', 'dist/cdn/video.js'); grunt.file.copy('build/files/video-js.min.css', 'dist/cdn/video-js.css'); grunt.file.copy('node_modules/videojs-swf/dist/video-js.swf', 'dist/cdn/video-js.swf'); grunt.file.copy('build/demo-files/demo.captions.vtt', 'dist/cdn/demo.captions.vtt'); grunt.file.copy('build/demo-files/demo.html', 'dist/cdn/demo.html'); // Replace font urls with CDN versions css = grunt.file.read('dist/cdn/video-js.css'); css = css.replace(/font\//g, '../f/3/'); grunt.file.write('dist/cdn/video-js.css', css); // Add CDN-specfic JS jsmin = grunt.file.read('dist/cdn/video.js'); // GA Tracking Pixel (manually building the pixel URL) cdnjs = uglify.minify('src/js/cdn.js').code.replace('v0.0.0', 'v'+version.full); grunt.file.write('dist/cdn/video.js', jsmin + cdnjs); }); grunt.registerTask('cdn-links', 'Update the version of CDN links in docs', function(){ var doc = grunt.file.read('docs/guides/setup.md'); var version = pkg.version; // remove the patch version to point to the latest patch version = version.replace(/(\d\.\d)\.\d/, '$1'); // update the version in http://vjs.zencdn.net/4.3/video.js doc = doc.replace(/(\/\/vjs\.zencdn\.net\/)\d\.\d(\.\d)?/g, '$1'+version); grunt.file.write('docs/guides/setup.md', doc); }); grunt.registerTask('dist', 'Creating distribution', ['dist-copy', 'zip:dist']); grunt.registerTask('next-issue', 'Get the next issue that needs a response', function(){ var done = this.async(); var GitHubApi = require('github'); var open = require('open'); var github = new GitHubApi({ // required version: '3.0.0', // optional debug: true, protocol: 'https', // host: 'github.my-GHE-enabled-company.com', // pathPrefix: '/api/v3', // for some GHEs timeout: 5000 }); github.issues.repoIssues({ // optional: // headers: { // 'cookie': 'blahblah' // }, user: 'videojs', repo: 'video.js', sort: 'updated', direction: 'asc', state: 'open', per_page: 100 }, function(err, res) { var issueToOpen; var usersWithWrite = ['heff', 'mmcc']; var categoryLabels = ['enhancement', 'bug', 'question', 'feature']; console.log('Number of issues: '+res.length); // TODO: Find the best way to exclude an issue where a question has been asked of the // issue owner/submitter that hasn't been answerd yet. // A stupid simple first step would be to check for the needs: more info label // and exactly one comment (the question) // find issues that need categorizing, no category labels res.some(function(issue){ if (issue.labels.length === 0) { return issueToOpen = issue; // break } // look for category labels var categorized = issue.labels.some(function(label){ return categoryLabels.indexOf(label.name) >= 0; }); if (!categorized) { return issueToOpen = issue; // break } }); if (issueToOpen) { open(issueToOpen.html_url); return done(); } // find issues that need confirming or answering res.some(function(issue){ // look for confirmed label var confirmed = issue.labels.some(function(label){ return label.name === 'confirmed'; }); // Was exluding questions, but that might leave a lot of people hanging // var question = issue.labels.some(function(label){ // return label.name === 'question'; // }); if (!confirmed) { // && !question return issueToOpen = issue; // break } }); if (issueToOpen) { open(issueToOpen.html_url); return done(); } grunt.log.writeln('No next issue found'); done(); }); }); };