diff --git a/app/templates/_package.json b/app/templates/_package.json index 09d2e0ea6..e2804bb0d 100644 --- a/app/templates/_package.json +++ b/app/templates/_package.json @@ -44,11 +44,14 @@ "gulp-angular-templatecache": "^1.7.0", "gulp-autoprefixer": "2.3.1",<% if(filters.babel) { %> "gulp-babel": "^5.1.0",<% } %> + "gulp-babel-istanbul": "^0.11.0", "gulp-cache": "^0.2.10", "gulp-concat": "^2.6.0", + "gulp-env": "^0.2.0", "gulp-filter": "^2.0.2", "gulp-imagemin": "^2.2.1", "gulp-inject": "^1.3.1", + "gulp-jscs": "^3.0.2", "gulp-jshint": "^1.11.0",<% if(filters.less) { %> "gulp-less": "3.0.3",<% } %> "gulp-livereload": "^3.8.0", @@ -58,6 +61,7 @@ "gulp-ng-annotate": "^1.1.0", "gulp-ng-constant": "^1.1.0", "gulp-plumber": "^1.0.1", + "gulp-protractor": "^2.1.0", "gulp-rename": "^1.2.2", "gulp-rev": "^5.0.0", "gulp-rev-replace": "^0.4.2", @@ -65,7 +69,7 @@ "gulp-sourcemaps": "^1.5.2", "gulp-svgmin": "^1.1.2", "gulp-uglify": "^1.2.0", - "gulp-useref": "^1.2.0", + "gulp-useref": "^3.0.3", "gulp-util": "^3.0.5", "gulp-watch": "^4.3.5",<% if(filters.jade) { %> "gulp-jade": "^1.0.1",<% } if(filters.stylus) { %> diff --git a/app/templates/gulpfile.babel(gulp).js b/app/templates/gulpfile.babel(gulp).js index 1266f8bef..228a43d66 100644 --- a/app/templates/gulpfile.babel(gulp).js +++ b/app/templates/gulpfile.babel(gulp).js @@ -11,45 +11,39 @@ import open from 'open'; import lazypipe from 'lazypipe'; import {stream as wiredep} from 'wiredep'; import nodemon from 'nodemon'; -import runSequence from 'run-sequence';<% if(filters.stylus) { %> +import {Server as KarmaServer} from 'karma'; +import runSequence from 'run-sequence'; +import {protractor, webdriver_update} from 'gulp-protractor';<% if(filters.stylus) { %> import nib from 'nib';<% } %> var plugins = gulpLoadPlugins(); var config; +const clientPath = require('./bower.json').appPath || 'client'; +const serverPath = 'server'; const paths = { - appPath: require('./bower.json').appPath || 'client', client: { - assets: 'client/assets/**/*', - images: 'client/assets/images/*', + assets: `${clientPath}/assets/**/*`, + images: `${clientPath}/assets/images/**/*`, scripts: [ - 'client/**/*.<%= scriptExt %>', - '!client/bower_components/**/*.js' + `${clientPath}/**/!(*.spec|*.mock).<%= scriptExt %>`, + `!${clientPath}/bower_components/**/*.js` ], - styles: ['client/{app,components}/**/*.<%= styleExt %>'], - mainStyle: 'client/app/app.<%= styleExt %>', - views: 'client/{app,components}/**/*.<%= templateExt %>', - mainView: 'client/index.html', - test: ['client/**/*.spec.<%= scriptExt %>'], - testRequire: [ - 'client/bower_components/angular/angular.js', - 'client/bower_components/angular-mocks/angular-mocks.js', - 'client/bower_components/angular-resource/angular-resource.js', - 'client/bower_components/angular-cookies/angular-cookies.js', - 'client/bower_components/angular-sanitize/angular-sanitize.js', - 'client/bower_components/angular-route/angular-route.js', - 'client/**/*.spec.<%= scriptExt %>' - ], - bower: 'client/bower_components/' + styles: [`${clientPath}/{app,components}/**/*.<%= styleExt %>`], + mainStyle: `${clientPath}/app/app.<%= styleExt %>`, + views: `${clientPath}/{app,components}/**/*.<%= templateExt %>`, + mainView: `${clientPath}/index.html`, + test: [`${clientPath}/{app,components}/**/*.{spec,mock}.<%= scriptExt %>`], + e2e: ['e2e/**/*.spec.js'], + bower: `${clientPath}/bower_components/` }, server: { - scripts: ['server/**/*.<%= scriptExt %>'], - json: ['server/**/*.json'], - test: [ - 'server/**/*.spec.js', - 'server/**/*.mock.js', - 'server/**/*.integration.js' - ] + scripts: [`${serverPath}/**/!(*.spec|*.integration).<%= scriptExt %>`], + json: [`${serverPath}/**/*.json`], + test: { + integration: `${serverPath}/**/*.integration.js`, + unit: `${serverPath}/**/*.spec.js` + } }, karma: 'karma.conf.js', dist: 'dist' @@ -91,6 +85,25 @@ function whenServerReady(cb) { 100); } +function sortModulesFirst(a, b) { + var module = /\.module\.js$/; + var aMod = module.test(a.path); + var bMod = module.test(b.path); + // inject *.module.js first + if (aMod === bMod) { + // either both modules or both non-modules, so just sort normally + if (a.path < b.path) { + return -1; + } + if (a.path > b.path) { + return 1; + } + return 0; + } else { + return (aMod ? -1 : 1); + } +} + /******************** * Reusable pipelines ********************/ @@ -98,13 +111,19 @@ function whenServerReady(cb) { let lintClientScripts = lazypipe()<% if(filters.coffee) { %> .pipe(plugins.coffeelint) .pipe(plugins.coffeelint.reporter);<% } else { %> - .pipe(plugins.jshint, 'client/.jshintrc') + .pipe(plugins.jshint, `${clientPath}/.jshintrc`) .pipe(plugins.jshint.reporter, 'jshint-stylish');<% } %> let lintServerScripts = lazypipe()<% if(filters.coffee) { %> .pipe(plugins.coffeelint) .pipe(plugins.coffeelint.reporter);<% } else { %> - .pipe(plugins.jshint, 'server/.jshintrc') + .pipe(plugins.jshint, `${serverPath}/.jshintrc`) + .pipe(plugins.jshint.reporter, 'jshint-stylish');<% } %> + +let lintServerTestScripts = lazypipe()<% if(filters.coffee) { %> + .pipe(plugins.coffeelint) + .pipe(plugins.coffeelint.reporter);<% } else { %> + .pipe(plugins.jshint, `${serverPath}/.jshintrc-spec`) .pipe(plugins.jshint.reporter, 'jshint-stylish');<% } %> let styles = lazypipe() @@ -118,7 +137,15 @@ let styles = lazypipe() .pipe(plugins.autoprefixer, {browsers: ['last 1 version']}) .pipe(plugins.sourcemaps.write, '.');<% if(filters.babel || filters.coffee) { %> -let transpile = lazypipe() +let transpileServer = lazypipe() + .pipe(plugins.sourcemaps.init)<% if(filters.babel) { %> + .pipe(plugins.babel, { + optional: ['runtime'] + })<% } else { %> + .pipe(plugins.coffee, {bare: true})<% } %> + .pipe(plugins.sourcemaps.write, '.'); + +let transpileClient = lazypipe() .pipe(plugins.sourcemaps.init)<% if(filters.babel) { %> .pipe(plugins.babel, { optional: ['es7.classProperties'] @@ -126,6 +153,28 @@ let transpile = lazypipe() .pipe(plugins.coffee, {bare: true})<% } %> .pipe(plugins.sourcemaps.write, '.');<% } %> +let mocha = lazypipe() + .pipe(plugins.mocha, { + reporter: 'spec', + timeout: 5000, + require: [ + './mocha.conf' + ] + }); + +let istanbul = lazypipe() + .pipe(plugins.babelIstanbul.writeReports) + .pipe(plugins.babelIstanbul.enforceThresholds, { + thresholds: { + global: { + lines: 80, + statements: 80, + branches: 80, + functions: 80 + } + } + }); + /******************** * Env ********************/ @@ -133,7 +182,7 @@ let transpile = lazypipe() gulp.task('env:all', () => { let localConfig; try { - localConfig = require('./server/config/local.env'); + localConfig = require(`./${serverPath}/config/local.env`); } catch (e) { localConfig = {}; } @@ -163,31 +212,31 @@ gulp.task('inject', cb => { gulp.task('inject:js', () => { return gulp.src(paths.client.mainView) .pipe(plugins.inject( - gulp.src(_.union(paths.client.scripts, ['!client/**/*.spec.<%= scriptExt %>']), {read: false}) - .pipe(plugins.sort()), + gulp.src(_.union(paths.client.scripts, [`!${clientPath}/**/*.{spec,mock}.js`, `!${clientPath}/app/app.js`]), {read: false}) + .pipe(plugins.sort(sortModulesFirst)), { starttag: '', endtag: '', - transform: (filepath) => '' + transform: (filepath) => '' })) - .pipe(gulp.dest('client')); + .pipe(gulp.dest(clientPath)); }); gulp.task('inject:css', () => { return gulp.src(paths.client.mainView) .pipe(plugins.inject( - gulp.src('/client/**/*.css', {read: false}) + gulp.src('/${clientPath}/{app,components}/**/*.css', {read: false}) .pipe(plugins.sort()), { starttag: '', endtag: '', - transform: (filepath) => '' + transform: (filepath) => '' })) - .pipe(gulp.dest('client')); + .pipe(gulp.dest(clientPath)); }); gulp.task('inject:<%= styleExt %>', () => { - return gulp.src('client/app/app.<%= styleExt %>') + return gulp.src(paths.client.mainStyle) .pipe(plugins.inject( gulp.src(_.union(paths.client.styles, ['!' + paths.client.mainStyle]), {read: false}) .pipe(plugins.sort()), @@ -196,14 +245,14 @@ gulp.task('inject:<%= styleExt %>', () => { endtag: '// endinjector', transform: (filepath) => { let newPath = filepath - .replace('/client/app/', '') - .replace('/client/components/', '../components/') + .replace(`/${clientPath}/app/`, '') + .replace(`/${clientPath}/components/`, '../components/') .replace(/_(.*).<%= styleExt %>/, (match, p1, offset, string) => p1) .replace('.<%= styleExt %>', ''); - return '@import \'' + newPath + '\';'; + return `@import '${newPath}';`; } })) - .pipe(gulp.dest('client/app')); + .pipe(gulp.dest(`${clientPath}/app`)); }); gulp.task('styles', () => { @@ -214,14 +263,14 @@ gulp.task('styles', () => { gulp.task('transpile:client', () => { return gulp.src(paths.client.scripts) - .pipe(transpile()) + .pipe(transpileClient()) .pipe(gulp.dest('.tmp')); });<% } %> gulp.task('transpile:server', () => { return gulp.src(_.union(paths.server.scripts, paths.server.json)) - .pipe(transpile()) - .pipe(gulp.dest(paths.dist + '/server')); + .pipe(transpileServer()) + .pipe(gulp.dest(`${paths.dist}/${serverPath}`)); }); gulp.task('lint:scripts', cb => runSequence(['lint:scripts:client', 'lint:scripts:server'], cb)); @@ -236,7 +285,23 @@ gulp.task('lint:scripts:server', () => { .pipe(lintServerScripts()); }); -gulp.task('clean:tmp', () => del(['.tmp/**/*'])); +gulp.task('lint:scripts:clientTest', () => { + return gulp.src(paths.client.test) + .pipe(lintClientScripts()); +}); + +gulp.task('lint:scripts:serverTest', () => { + return gulp.src(paths.server.test) + .pipe(lintServerTestScripts()); +}); + +gulp.task('jscs', () => { + return gulp.src(_.union(paths.client.scripts, paths.server.scripts)) + .pipe(plugins.jscs()) + .pipe(plugins.jscs.reporter()); +}); + +gulp.task('clean:tmp', () => del(['.tmp/**/*'], {dot: true})); gulp.task('start:client', cb => { whenServerReady(() => { @@ -245,15 +310,22 @@ gulp.task('start:client', cb => { }); }); +gulp.task('start:server:prod', () => { + process.env.NODE_ENV = process.env.NODE_ENV || 'production'; + config = require(`./${paths.dist}/${serverPath}/config/environment`); + nodemon(`-w ${paths.dist}/${serverPath} ${paths.dist}/${serverPath}`) + .on('log', onServerLog); +}); + gulp.task('start:server', () => { process.env.NODE_ENV = process.env.NODE_ENV || 'development'; - config = require('./server/config/environment'); - nodemon('-w server server') + config = require(`./${serverPath}/config/environment`); + nodemon(`-w ${serverPath} ${serverPath}`) .on('log', onServerLog); }); gulp.task('watch', () => { - var testFiles = _.union(paths.client.test, paths.server.test); + var testFiles = _.union(paths.client.test, paths.server.test.unit, paths.server.test.integration); plugins.livereload.listen(); @@ -271,7 +343,7 @@ gulp.task('watch', () => { plugins.watch(paths.client.scripts) //['inject:js'] .pipe(plugins.plumber())<% if(filters.babel || filters.coffee) { %> - .pipe(transpile()) + .pipe(transpileClient()) .pipe(gulp.dest('.tmp'))<% } %> .pipe(plugins.livereload()); @@ -294,7 +366,16 @@ gulp.task('serve', cb => { cb); }); -gulp.task('test', cb => { +gulp.task('serve:dist', cb => { + runSequence( + 'build', + 'env:all', + 'env:prod', + ['start:server:prod', 'start:client'], + cb); +}); + +gulp.task('test', ['wiredep:test'], cb => { return runSequence('test:server', 'test:client', cb); }); @@ -303,30 +384,26 @@ gulp.task('test:server', cb => { 'env:all', 'env:test', 'mocha:unit', + 'mocha:integration', //'mocha:coverage', cb); }); gulp.task('mocha:unit', () => { - return gulp.src(paths.server.test) - .pipe(plugins.mocha({ - reporter: 'spec', - require: [ - './mocha.conf' - ] - })) - .once('end', function() { - process.exit(); - }); + return gulp.src(paths.server.test.unit) + .pipe(mocha()); }); -gulp.task('test:client', () => { - let testFiles = _.union(paths.client.testRequire, paths.client.test); - return gulp.src(testFiles) - .pipe(plugins.karma({ - configFile: paths.karma, - action: 'watch' - })); +gulp.task('mocha:integration', () => { + return gulp.src(paths.server.test.integration) + .pipe(mocha()); +}); + +gulp.task('test:client', (done) => { + new KarmaServer({ + configFile: `${__dirname}/${paths.karma}`, + singleRun: true + }, done).start(); }); // inject bower components @@ -341,9 +418,9 @@ gulp.task('wiredep:client', () => { /bootstrap.css/, /font-awesome.css/ ], - ignorePath: paths.appPath + ignorePath: clientPath })) - .pipe(gulp.dest('client/')); + .pipe(gulp.dest(`${clientPath}/`)); }); gulp.task('wiredep:test', () => { @@ -370,6 +447,7 @@ gulp.task('wiredep:test', () => { gulp.task('build', cb => { runSequence( 'clean:dist', + 'clean:tmp', 'inject', 'wiredep:client', [ @@ -383,20 +461,20 @@ gulp.task('build', cb => { cb); }); -gulp.task('clean:dist', () => del(['dist/**/*'])); +gulp.task('clean:dist', () => del([`${paths.dist}/!(.git*|.openshift|Procfile)**`], {dot: true})); gulp.task('build:client', ['transpile:client', 'styles', 'html'], () => { + var manifest = gulp.src(`${paths.dist}/${clientPath}/assets/rev-manifest.json`); + var appFilter = plugins.filter('**/app.js'); var jsFilter = plugins.filter('**/*.js'); var cssFilter = plugins.filter('**/*.css'); - var htmlFilter = plugins.filter('**/*.html');<% if(filters.jade) { %> + var htmlBlock = plugins.filter(['**/*.!(html)']);<% if(filters.jade) { %> var assetsFilter = plugins.filter('**/*.{js,css}');<% } %> - let assets = plugins.useref.assets({searchPath: ['client', '.tmp']}); - return gulp.src(paths.client.mainView)<% if(filters.jade) { %> .pipe(plugins.jade({pretty: true}))<% } %> - .pipe(assets) + .pipe(plugins.useref()) .pipe(appFilter) .pipe(plugins.addSrc.append('.tmp/templates.js')) .pipe(plugins.concat('app/app.js')) @@ -411,16 +489,16 @@ gulp.task('build:client', ['transpile:client', 'styles', 'html'], () => { processImportFrom: ['!fonts.googleapis.com'] })) .pipe(cssFilter.restore()) - .pipe(plugins.rev()) - .pipe(assets.restore()) - .pipe(plugins.revReplace()) - .pipe(plugins.useref())<% if(filters.jade) { %> + .pipe(htmlBlock) + .pipe(plugins.rev()) + .pipe(htmlBlock.restore()) + .pipe(plugins.revReplace({manifest}))<% if(filters.jade) { %> .pipe(assetsFilter)<% } %> - .pipe(gulp.dest(paths.dist + '/client')); + .pipe(gulp.dest(`${paths.dist}/${clientPath}`)); }); gulp.task('html', function() { - return gulp.src('client/{app,components}/**/*.html') + return gulp.src(`${clientPath}/{app,components}/**/*.html`) .pipe(plugins.angularTemplatecache({ module: '<%= scriptAppName %>' })) @@ -433,7 +511,7 @@ gulp.task('jade', function() { });<% } %> gulp.task('constant', function() { - let sharedConfig = require('./server/config/environment/shared'); + let sharedConfig = require(`./${serverPath}/config/environment/shared`); plugins.ngConstant({ name: '<%= scriptAppName %>.constants', deps: [], @@ -444,30 +522,37 @@ gulp.task('constant', function() { .pipe(plugins.rename({ basename: 'app.constant' })) - .pipe(gulp.dest('client/app/')) + .pipe(gulp.dest(`${clientPath}/app/`)) }) gulp.task('build:images', () => { - return gulp.src('client/assets/images/**/*') + return gulp.src(paths.client.images) .pipe(plugins.imagemin({ optimizationLevel: 5, progressive: true, interlaced: true })) - .pipe(gulp.dest(paths.dist + '/client/assets/images')); + .pipe(plugins.rev()) + .pipe(gulp.dest(`${paths.dist}/${clientPath}/assets/images`)) + .pipe(plugins.rev.manifest(`${paths.dist}/${clientPath}/assets/rev-manifest.json`, { + base: `${paths.dist}/${clientPath}/assets`, + merge: true + })) + .pipe(gulp.dest(`${paths.dist}/${clientPath}/assets`)); }); gulp.task('copy:extras', () => { return gulp.src([ - 'client/favicon.ico', - 'client/robots.txt' + `${clientPath}/favicon.ico`, + `${clientPath}/robots.txt`, + `${clientPath}/.htaccess` ], { dot: true }) - .pipe(gulp.dest(paths.dist + '/client')); + .pipe(gulp.dest(`${paths.dist}/${clientPath}`)); }); gulp.task('copy:assets', () => { return gulp.src([paths.client.assets, '!' + paths.client.images]) - .pipe(gulp.dest(paths.dist + '/client/assets')); + .pipe(gulp.dest(`${paths.dist}/${clientPath}/assets`)); }); gulp.task('copy:server', () => { @@ -478,3 +563,48 @@ gulp.task('copy:server', () => { ], {cwdbase: true}) .pipe(gulp.dest(paths.dist)); }); + +gulp.task('coverage:pre', () => { + return gulp.src(paths.server.scripts) + // Covering files + .pipe(plugins.babelIstanbul()) + // Force `require` to return covered files + .pipe(plugins.babelIstanbul.hookRequire()); +}); + +gulp.task('coverage:unit', () => { + return gulp.src(paths.server.test.unit) + .pipe(mocha()) + .pipe(istanbul()) + // Creating the reports after tests ran +}); + +gulp.task('coverage:integration', () => { + return gulp.src(paths.server.test.integration) + .pipe(mocha()) + .pipe(istanbul()) + // Creating the reports after tests ran +}); + +gulp.task('mocha:coverage', cb => { + runSequence('coverage:pre', + 'env:all', + 'env:test', + 'coverage:unit', + 'coverage:integration', + cb); +}); + +// Downloads the selenium webdriver +gulp.task('webdriver_update', webdriver_update); + +gulp.task('test:e2e', ['env:all', 'env:test', 'start:server', 'webdriver_update'], cb => { + gulp.src(paths.client.e2e) + .pipe(protractor({ + configFile: 'protractor.conf.js', + })).on('error', err => { + console.log(err) + }).on('end', () => { + process.exit(); + }); +});