var _ = require('lodash'); var buildConfig = require('./config/build.config.js'); var changelog = require('conventional-changelog'); var connect = require('connect'); var dgeni = require('dgeni'); var es = require('event-stream'); var htmlparser = require('htmlparser2'); var lunr = require('lunr'); var mkdirp = require('mkdirp'); var yaml = require('js-yaml'); var http = require('http'); var cp = require('child_process'); var fs = require('fs'); var gulp = require('gulp'); var pkg = require('./package.json'); var semver = require('semver'); var through = require('through'); var argv = require('minimist')(process.argv.slice(2)); var concat = require('gulp-concat'); var gulpif = require('gulp-if'); var header = require('gulp-header'); var jshint = require('gulp-jshint'); var minifyCss = require('gulp-minify-css'); var rename = require('gulp-rename'); var sass = require('gulp-sass'); var stripDebug = require('gulp-strip-debug'); var template = require('gulp-template'); var uglify = require('gulp-uglify'); var gutil = require('gulp-util'); var banner = _.template(buildConfig.banner, { pkg: pkg }); var IS_RELEASE_BUILD = !!argv.release; if (IS_RELEASE_BUILD) { gutil.log( gutil.colors.red('--release:'), 'Building release version (minified, debugs stripped)...' ); } gulp.task('default', ['build']); gulp.task('build', ['bundle', 'sass']); gulp.task('docs-index', function() { var idx = lunr(function() { this.field('path'); this.field('title', {boost: 10}); this.field('body'); this.ref('path') }); var ref = {}; return gulp.src([ 'tmp/ionic-site/docs/{components,guide,overview,angularjs}/**/*.{md,html}', 'tmp/ionic-site/tutorials/**/*.{md,html}' ]) .pipe(es.map(function(file, callback) { //docs for gulp file objects: https://github.com/wearefractal/vinyl var contents = file.contents.toString(); //was buffer // Grab relative path from ionic-site root var relpath = file.path.replace(/^.*?tmp\/ionic-site\//, ''); // Read out the yaml portion of the Jekyll file var title, layout; var yamlStartIndex = contents.indexOf('---'); var yamlEndIndex = contents.indexOf('---', yamlStartIndex+3); //starting from start var yamlRaw = contents.substring(yamlStartIndex+3, yamlEndIndex); var properties = yaml.safeLoad(yamlRaw); contents = contents.substring(yamlEndIndex+3); if(properties.title && properties.layout) { title = properties.title; layout = properties.layout; } else { return callback('layout and title properties not found in Jekyll file '+relpath); } var body = ''; // Parse all html and use only text portion var parser = new htmlparser.Parser({ ontext: function(text){ // Ignore any Jekyll expressions body += text.replace(/{%.*%}/, '', 'g'); }, }); parser.write(contents); parser.end(); // Add the data to the indexer and ref object idx.add({'path': relpath, 'body': body, 'title': title}); ref[relpath] = {'title': title, 'layout': layout}; callback(); })).on('end', function() { // Write out as one json file mkdirp.sync('tmp/ionic-site/data'); fs.writeFileSync('tmp/ionic-site/data/index.json', JSON.stringify({'ref': ref, 'index': idx.toJSON()})); }); }); gulp.task('docs', function(done) { var docVersion = argv['doc-version']; if (docVersion != 'nightly' && !semver.valid(docVersion)) { console.log('Usage: gulp docs --doc-version=(nightly|versionName)'); return process.exit(1); } process.env.DOC_VERSION = docVersion; return dgeni('docs/docs.config.js').generateDocs().then(function() { gutil.log('Docs for', gutil.colors.cyan(docVersion), 'generated!'); }); }); var IS_WATCH = false; gulp.task('watch', ['bundle'], function() { IS_WATCH = true; gulp.watch('js/**/*.js', ['bundle']); gulp.watch('scss/**/*.scss', ['sass']); }); gulp.task('changelog', function(done) { changelog({ repository: pkg.repository.url, version: pkg.version, }, function(err, data) { if (err) return done(err); require('fs').writeFileSync('CHANGELOG.md', data); done(); }); }); gulp.task('bundle', [ 'scripts', 'scripts-ng', 'vendor', 'version', ], function() { IS_RELEASE_BUILD && gulp.src(buildConfig.ionicBundleFiles.map(function(src) { return src.replace(/.js$/, '.min.js'); })) .pipe(header(buildConfig.bundleBanner)) .pipe(concat('ionic.bundle.min.js')) .pipe(gulp.dest(buildConfig.distJs)); return gulp.src(buildConfig.ionicBundleFiles) .pipe(header(buildConfig.bundleBanner)) .pipe(concat('ionic.bundle.js')) .pipe(gulp.dest(buildConfig.distJs)); }); gulp.task('jshint', function() { return gulp.src(['js/**/*.js', 'test/**/*.js']) .pipe(jshint('.jshintrc')) .pipe(jshint.reporter('jshint-stylish')); }); gulp.task('ddescribe-iit', function() { return gulp.src(['test/**/*.js', 'js/**/*.js']) .pipe(notContains([ 'ddescribe', 'iit', 'xit', 'xdescribe' ])); }); gulp.task('vendor', function() { return gulp.src(buildConfig.vendorFiles, { cwd: 'config/lib/', base: 'config/lib/' }) .pipe(gulp.dest(buildConfig.dist)); }); gulp.task('scripts', function() { return gulp.src(buildConfig.ionicFiles) .pipe(gulpif(IS_RELEASE_BUILD, stripDebug())) .pipe(concat('ionic.js')) .pipe(header(banner)) .pipe(gulp.dest(buildConfig.distJs)) .pipe(gulpif(IS_RELEASE_BUILD, uglify())) .pipe(rename({ extname: '.min.js' })) .pipe(header(banner)) .pipe(gulp.dest(buildConfig.distJs)); }); gulp.task('scripts-ng', function() { return gulp.src(buildConfig.angularIonicFiles) // .pipe(gulpif(IS_RELEASE_BUILD, stripDebug())) .pipe(concat('ionic-angular.js')) .pipe(header(banner)) .pipe(gulp.dest(buildConfig.distJs)) .pipe(gulpif(IS_RELEASE_BUILD, uglify())) .pipe(rename({ extname: '.min.js' })) .pipe(header(banner)) .pipe(gulp.dest(buildConfig.distJs)); }); gulp.task('sass', function(done) { gulp.src('scss/ionic.scss') .pipe(header(banner)) .pipe(sass({ onError: function(err) { //If we're watching, don't exit on error if (IS_WATCH) { console.log(gutil.colors.red(err)); } else { done(err); } } })) .pipe(concat('ionic.css')) .pipe(header(banner)) .pipe(gulp.dest(buildConfig.distCss)) .pipe(gulpif(IS_RELEASE_BUILD, minifyCss({ keepSpecialComments: 0 }))) .pipe(rename({ extname: '.min.css' })) .pipe(header(banner)) .pipe(gulp.dest(buildConfig.distCss)) .on('end', done); }); gulp.task('version', function() { var d = new Date(); var date = d.toISOString().substring(0,10); var time = pad(d.getUTCHours()) + ':' + pad(d.getUTCMinutes()) + ':' + pad(d.getUTCSeconds()); return gulp.src('config/version.template.json') .pipe(template({ pkg: pkg, date: date, time: time })) .pipe(rename('version.json')) .pipe(gulp.dest('dist')); }); gulp.task('sauce-connect', sauceConnect); gulp.task('cloudtest', ['protractor-sauce'], function(cb) { sauceDisconnect(cb); }); gulp.task('karma', function(cb) { return karma(cb, ['config/karma.conf.js', '--single-run=true']); }); gulp.task('karma-watch', function(cb) { return karma(cb, ['config/karma.conf.js']); }); var connectServer; gulp.task('connect-server', function() { var app = connect().use(connect.static(__dirname)); connectServer = http.createServer(app).listen(8765); }); gulp.task('protractor', ['connect-server'], function(cb) { return protractor(cb, ['config/protractor.conf.js']); }); gulp.task('protractor-sauce', ['sauce-connect', 'connect-server'], function(cb) { return protractor(cb, ['config/protractor-sauce.conf.js']); }); function karma(cb, args) { if (argv.browsers) { args.push('--browsers='+argv.browsers.trim()); } if (argv.reporters) { args.push('--reporters='+argv.reporters.trim()); } cp.spawn('node', [ './node_modules/karma/bin/karma', 'start' ].concat(args), { stdio: 'inherit' }) .on('exit', function(code) { if (code) return cb('Karma test(s) failed. Exit code: ' + code); cb(); }); } function pad(n) { if (n<10) { return '0' + n; } return n; } function protractor(cb, args) { cp.spawn('protractor', args, { stdio: 'inherit' }) .on('exit', function(code) { connectServer && connectServer.close(); if (code) return cb('Protector test(s) failed. Exit code: ' + code); cb(); }); } var sauceInstance; function sauceConnect(cb) { require('sauce-connect-launcher')({ username: process.env.SAUCE_USER, accessKey: process.env.SAUCE_KEY, verbose: true, tunnelIdentifier: process.env.TRAVIS_BUILD_NUMBER }, function(err, instance) { if (err) return cb('Failed to launch sauce connect!'); sauceInstance = instance; cb(); }); } function sauceDisconnect(cb) { if (sauceInstance) { return sauceInstance.close(cb); } cb(); } function notContains(disallowed) { disallowed = disallowed || []; return through(function(file) { var error; var contents = file.contents.toString(); disallowed.forEach(function(str) { var idx = disallowedIndex(contents, str); if (idx !== -1) { error = error || file.path + ' contains ' + str + ' on line ' + contents.substring(0, idx, str).split('\n').length + '!'; } }); if (error) { throw new Error(error); } else { this.emit('data', file); } }); function disallowedIndex(content, disallowedString) { var notFunctionName = '[^A-Za-z0-9$_]'; var regex = new RegExp('(^|' + notFunctionName + ')(' + disallowedString + ')' + notFunctionName + '*\\(', 'gm'); var match = regex.exec(content); // Return the match accounting for the first submatch length. return match !== null ? match.index + match[1].length : -1; } }