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();
+ });
+});