2014-05-03 17:34:41 +04:00
// # Task automation for Ghost
//
// Run various tasks when developing for and working with Ghost.
//
// **Usage instructions:** can be found in the [Custom Tasks](#custom%20tasks) section or by running `grunt --help`.
//
// **Debug tip:** If you have any problems with any Grunt tasks, try running them with the `--verbose` command
2014-08-02 19:21:38 +04:00
var _ = require ( 'lodash' ) ,
2015-05-19 18:51:53 +03:00
chalk = require ( 'chalk' ) ,
2014-08-02 19:21:38 +04:00
fs = require ( 'fs-extra' ) ,
2015-01-06 23:45:46 +03:00
moment = require ( 'moment' ) ,
2014-08-02 19:21:38 +04:00
getTopContribs = require ( 'top-gh-contribs' ) ,
path = require ( 'path' ) ,
Promise = require ( 'bluebird' ) ,
request = require ( 'request' ) ,
2014-08-07 02:17:14 +04:00
escapeChar = process . platform . match ( /^win/ ) ? '^' : '\\' ,
2014-08-08 19:14:15 +04:00
cwd = process . cwd ( ) . replace ( /( |\(|\))/g , escapeChar + '$1' ) ,
2014-08-07 02:17:14 +04:00
buildDirectory = path . resolve ( cwd , '.build' ) ,
distDirectory = path . resolve ( cwd , '.dist' ) ,
2015-01-06 00:43:11 +03:00
mochaPath = path . resolve ( cwd + '/node_modules/grunt-mocha-cli/node_modules/mocha/bin/mocha' ) ,
2015-03-19 20:00:13 +03:00
emberPath = path . resolve ( cwd + '/core/client/node_modules/.bin/ember' ) ,
2013-09-13 11:24:28 +04:00
2013-12-06 14:24:25 +04:00
// ## Grunt configuration
2013-06-25 15:43:15 +04:00
configureGrunt = function ( grunt ) {
2014-05-03 17:34:41 +04:00
// #### Load all grunt tasks
//
// Find all of the task which start with `grunt-` and load them, rather than explicitly declaring them all
2014-02-19 07:41:21 +04:00
require ( 'matchdep' ) . filterDev ( [ 'grunt-*' , '!grunt-cli' ] ) . forEach ( grunt . loadNpmTasks ) ;
2013-08-17 22:39:02 +04:00
2013-06-25 15:43:15 +04:00
var cfg = {
2014-05-03 17:34:41 +04:00
// #### Common paths used by tasks
2013-06-25 15:43:15 +04:00
paths : {
build : buildDirectory ,
2013-11-01 19:33:49 +04:00
releaseBuild : path . join ( buildDirectory , 'release' ) ,
2013-06-25 15:43:15 +04:00
dist : distDirectory ,
2013-10-11 19:31:47 +04:00
releaseDist : path . join ( distDirectory , 'release' )
2013-06-25 15:43:15 +04:00
} ,
2014-05-03 17:34:41 +04:00
// Standard build type, for when we have nightlies again.
2013-07-11 18:24:33 +04:00
buildType : 'Build' ,
2014-05-03 17:34:41 +04:00
// Load package.json so that we can create correctly versioned releases.
2013-06-25 15:43:15 +04:00
pkg : grunt . file . readJSON ( 'package.json' ) ,
2014-05-03 17:34:41 +04:00
// ### grunt-contrib-watch
// Watch files and livereload in the browser during development.
// See the [grunt dev](#live%20reload) task for how this is used.
2013-08-17 22:39:02 +04:00
watch : {
livereload : {
files : [
2014-08-06 04:26:42 +04:00
'content/themes/casper/assets/css/*.css' ,
'content/themes/casper/assets/js/*.js' ,
2015-06-23 06:08:00 +03:00
'core/built/assets/*.js' ,
'core/client/dist/index.html'
2013-08-17 22:39:02 +04:00
] ,
options : {
livereload : true
}
} ,
express : {
2015-06-05 22:48:04 +03:00
files : [ 'core/ghost-server.js' , 'core/server/**/*.js' ] ,
2013-08-17 22:39:02 +04:00
tasks : [ 'express:dev' ] ,
options : {
2015-06-05 22:48:04 +03:00
spawn : false
2013-08-17 22:39:02 +04:00
}
2015-05-17 15:33:41 +03:00
} ,
csscomb : {
files : [ 'core/client/app/styles/**/*.css' ] ,
tasks : [ 'shell:csscombfix' ] ,
options : {
livereload : true
}
2013-08-17 22:39:02 +04:00
}
} ,
2014-05-03 17:34:41 +04:00
// ### grunt-express-server
// Start a Ghost expess server for use in development and testing
2013-08-17 22:39:02 +04:00
express : {
options : {
2013-12-05 00:20:16 +04:00
script : 'index.js' ,
output : 'Ghost is running'
2013-08-17 22:39:02 +04:00
} ,
dev : {
2014-05-03 17:34:41 +04:00
options : { }
2013-08-29 14:04:33 +04:00
} ,
test : {
options : {
node _env : 'testing'
}
2013-08-17 22:39:02 +04:00
}
} ,
2014-05-03 17:34:41 +04:00
// ### grunt-contrib-jshint
// Linting rules, run as part of `grunt validate`. See [grunt validate](#validate) and its subtasks for
// more information.
2015-02-19 02:47:55 +03:00
jshint : {
options : {
jshintrc : true
} ,
2014-09-03 19:42:55 +04:00
2015-02-19 02:47:55 +03:00
client : [
'core/client/**/*.js' ,
'!core/client/node_modules/**/*.js' ,
'!core/client/bower_components/**/*.js' ,
'!core/client/tmp/**/*.js' ,
'!core/client/dist/**/*.js' ,
2015-03-04 22:46:24 +03:00
'!core/client/vendor/**/*.js'
2015-02-19 02:47:55 +03:00
] ,
server : [
'*.js' ,
'!config*.js' , // note: i added this, do we want this linted?
'core/*.js' ,
'core/server/**/*.js' ,
'core/test/**/*.js' ,
2015-04-18 15:20:03 +03:00
'!core/test/coverage/**' ,
2015-02-19 02:47:55 +03:00
'!core/shared/vendor/**/*.js'
]
} ,
jscs : {
options : {
config : true
} ,
client : {
options : {
2015-10-28 14:36:45 +03:00
config : 'core/client/.jscsrc'
2014-11-27 07:06:24 +03:00
} ,
2015-02-19 02:47:55 +03:00
files : {
src : [
'core/client/**/*.js' ,
'!core/client/node_modules/**/*.js' ,
'!core/client/bower_components/**/*.js' ,
2015-10-28 14:36:45 +03:00
'!core/client/tests/**/*.js' ,
2015-02-19 02:47:55 +03:00
'!core/client/tmp/**/*.js' ,
'!core/client/dist/**/*.js' ,
2015-03-04 22:46:24 +03:00
'!core/client/vendor/**/*.js'
2015-02-19 02:47:55 +03:00
]
2014-09-03 19:42:55 +04:00
}
2015-02-19 02:47:55 +03:00
} ,
2014-09-10 08:06:24 +04:00
2015-10-28 14:36:45 +03:00
client _tests : {
options : {
config : 'core/client/tests/.jscsrc'
} ,
files : {
src : [
'core/client/tests/**/*.js'
]
}
} ,
2015-02-19 02:47:55 +03:00
server : {
files : {
src : [
'*.js' ,
'!config*.js' , // note: i added this, do we want this linted?
'core/*.js' ,
'core/server/**/*.js' ,
'core/test/**/*.js' ,
2015-04-18 15:20:03 +03:00
'!core/test/coverage/**' ,
2015-02-19 02:47:55 +03:00
'!core/shared/vendor/**/*.js'
]
}
}
} ,
2013-06-25 15:43:15 +04:00
2014-05-03 17:34:41 +04:00
// ### grunt-mocha-cli
// Configuration for the mocha test runner, used to run unit, integration and route tests as part of
// `grunt validate`. See [grunt validate](#validate) and its sub tasks for more information.
2013-07-10 02:54:57 +04:00
mochacli : {
2013-06-25 15:43:15 +04:00
options : {
2013-09-24 14:46:30 +04:00
ui : 'bdd' ,
2014-09-03 23:50:19 +04:00
reporter : grunt . option ( 'reporter' ) || 'spec' ,
timeout : '15000' ,
save : grunt . option ( 'reporter-output' )
2013-05-26 21:17:46 +04:00
} ,
2014-05-03 17:34:41 +04:00
// #### All Unit tests
2013-11-24 18:29:36 +04:00
unit : {
2014-07-17 18:33:21 +04:00
src : [
'core/test/unit/**/*_spec.js'
]
2013-06-25 15:43:15 +04:00
} ,
2013-05-25 20:48:15 +04:00
2014-05-03 17:34:41 +04:00
// ##### Groups of unit tests
2013-08-20 01:52:50 +04:00
server : {
src : [ 'core/test/unit/**/server*_spec.js' ]
} ,
2014-10-10 18:54:07 +04:00
helpers : {
src : [ 'core/test/unit/server_helpers/*_spec.js' ]
} ,
2015-05-18 21:12:42 +03:00
middleware : {
src : [ 'core/test/unit/middleware/*_spec.js' ]
} ,
2014-05-03 17:34:41 +04:00
showdown : {
src : [ 'core/test/unit/**/showdown*_spec.js' ]
2013-08-25 00:51:58 +04:00
} ,
2013-06-25 15:43:15 +04:00
perm : {
2013-07-31 11:33:28 +04:00
src : [ 'core/test/unit/**/permissions_spec.js' ]
2013-08-03 19:11:16 +04:00
} ,
migrate : {
src : [
'core/test/unit/**/export_spec.js' ,
'core/test/unit/**/import_spec.js'
]
2013-10-30 01:34:47 +04:00
} ,
2013-12-04 06:47:39 +04:00
storage : {
src : [ 'core/test/unit/**/storage*_spec.js' ]
} ,
2014-05-03 17:34:41 +04:00
// #### All Integration tests
2013-10-30 01:34:47 +04:00
integration : {
2014-02-26 21:51:01 +04:00
src : [
2015-11-20 15:04:49 +03:00
'core/test/integration/**/*_spec.js' ,
'core/test/integration/**/*_spec.js' ,
2014-05-04 05:30:30 +04:00
'core/test/integration/*_spec.js'
2014-02-26 21:51:01 +04:00
]
2013-11-24 18:29:36 +04:00
} ,
2014-05-03 17:34:41 +04:00
// ##### Model integration tests
model : {
src : [ 'core/test/integration/**/model*_spec.js' ]
} ,
// ##### API integration tests
api : {
src : [ 'core/test/integration/**/api*_spec.js' ]
} ,
// #### All Route tests
2013-12-31 21:09:49 +04:00
routes : {
2014-07-17 18:33:21 +04:00
src : [
2015-04-18 15:20:03 +03:00
'core/test/functional/routes/**/*_spec.js'
2014-07-17 18:33:21 +04:00
]
2014-09-27 21:26:26 +04:00
} ,
// #### All Module tests
module : {
src : [
2015-04-18 15:20:03 +03:00
'core/test/functional/module/**/*_spec.js'
2014-09-27 21:26:26 +04:00
]
2013-06-25 15:43:15 +04:00
}
} ,
2013-06-15 02:12:04 +04:00
2015-04-18 15:20:03 +03:00
// ### grunt-mocha-istanbul
// Configuration for the mocha test coverage generator
// `grunt coverage`.
mocha _istanbul : {
coverage : {
// TODO fix the timing/async & cleanup issues with the route and integration tests so that
// they can also have coverage generated for them & the order doesn't matter
2015-05-28 18:16:09 +03:00
src : [ 'core/test/unit' ] ,
2015-04-18 15:20:03 +03:00
options : {
mask : '**/*_spec.js' ,
2015-07-28 17:02:00 +03:00
coverageFolder : 'core/test/coverage/unit' ,
2015-04-18 15:20:03 +03:00
mochaOptions : [ '--timeout=15000' ] ,
excludes : [ 'core/client/**' ]
}
2015-07-28 17:02:00 +03:00
} ,
coverage _integration : {
src : [ 'core/test/integration/api' ] ,
options : {
coverageFolder : 'core/test/coverage/integration' ,
mask : '**/*_spec.js' ,
mochaOptions : [ '--timeout=15000' ] ,
excludes : [ 'core/client/**' , 'core/server/built' , 'core/server/apps' , 'core/server/config' , 'core/server/data' ]
}
2015-04-18 15:20:03 +03:00
}
} ,
2015-02-14 21:43:18 +03:00
// ### grunt-bg-shell
// Used to run ember-cli watch in the background
bgShell : {
ember : {
2015-03-19 20:00:13 +03:00
cmd : emberPath + ' build --watch' ,
2015-02-14 21:43:18 +03:00
execOpts : {
cwd : path . resolve ( cwd + '/core/client/' )
} ,
bg : true ,
stdout : function ( out ) {
2015-05-19 18:51:53 +03:00
grunt . log . writeln ( chalk . cyan ( 'Ember-cli::' ) + out ) ;
2015-02-14 21:43:18 +03:00
} ,
stderror : function ( error ) {
2015-05-19 18:51:53 +03:00
grunt . log . error ( chalk . red ( 'Ember-cli::' + error ) ) ;
2015-02-14 21:43:18 +03:00
}
}
} ,
2014-05-03 17:34:41 +04:00
// ### grunt-shell
// Command line tools where it's easier to run a command directly than configure a grunt plugin
2013-06-25 15:43:15 +04:00
shell : {
2015-02-14 21:43:18 +03:00
ember : {
command : function ( mode ) {
switch ( mode ) {
case 'init' :
return 'echo Installing client dependencies... && npm install' ;
case 'prod' :
2015-03-19 20:00:13 +03:00
return emberPath + ' build --environment=production --silent' ;
2015-02-14 21:43:18 +03:00
case 'dev' :
2015-03-31 10:43:45 +03:00
return emberPath + ' build' ;
2015-02-18 23:02:48 +03:00
case 'test' :
2015-03-19 20:00:13 +03:00
return emberPath + ' test --silent' ;
2015-02-14 21:43:18 +03:00
}
} ,
options : {
execOptions : {
2015-07-06 17:21:16 +03:00
cwd : path . resolve ( process . cwd ( ) + '/core/client/' ) ,
2015-02-14 21:43:18 +03:00
stdout : false
}
}
} ,
2014-05-03 17:34:41 +04:00
// #### Run bower install
// Used as part of `grunt init`. See the section on [Building Assets](#building%20assets) for more
// information.
2014-03-05 01:35:06 +04:00
bower : {
2014-08-07 02:17:14 +04:00
command : path . resolve ( cwd + '/node_modules/.bin/bower --allow-root install' ) ,
2014-03-05 01:35:06 +04:00
options : {
2014-08-06 01:07:01 +04:00
stdout : true ,
stdin : false
2014-03-05 01:35:06 +04:00
}
} ,
2014-08-30 22:52:05 +04:00
2014-12-24 16:51:16 +03:00
test : {
command : function ( test ) {
2015-05-28 18:16:09 +03:00
return 'node ' + mochaPath + ' --timeout=15000 --ui=bdd --reporter=spec --colors core/test/' + test ;
2014-12-24 16:51:16 +03:00
}
} ,
2015-02-27 20:15:33 +03:00
shrinkwrap : {
command : 'npm shrinkwrap'
2015-05-16 14:43:12 +03:00
} ,
2015-09-23 16:21:11 +03:00
dedupe : {
command : 'npm dedupe'
} ,
2015-05-17 15:33:41 +03:00
csscombfix : {
2015-05-16 14:43:12 +03:00
command : path . resolve ( cwd + '/node_modules/.bin/csscomb -c core/client/app/styles/csscomb.json -v core/client/app/styles' )
2015-05-17 15:33:41 +03:00
} ,
csscomblint : {
command : path . resolve ( cwd + '/node_modules/.bin/csscomb -c core/client/app/styles/csscomb.json -lv core/client/app/styles' )
2013-06-25 15:43:15 +04:00
}
} ,
2013-06-02 03:45:02 +04:00
2014-05-03 17:34:41 +04:00
// ### grunt-docker
// Generate documentation from code
2014-05-03 15:38:59 +04:00
docker : {
2014-05-03 17:34:41 +04:00
docs : {
2014-05-05 19:26:19 +04:00
dest : 'docs' ,
2014-05-03 15:38:59 +04:00
src : [ '.' ] ,
options : {
2014-05-05 19:26:19 +04:00
onlyUpdated : true ,
2015-05-28 18:16:09 +03:00
exclude : 'node_modules,bower_components,content,core/client,*test,*doc*,' +
'*vendor,config.js,*buil*,.dist*,.idea,.git*,.travis.yml,.bower*,.editorconfig,.js*,*.md' ,
2014-05-03 15:38:59 +04:00
extras : [ 'fileSearch' ]
}
}
} ,
2014-05-03 17:34:41 +04:00
// ### grunt-contrib-clean
2013-12-06 14:24:25 +04:00
// Clean up files as part of other tasks
2013-09-18 22:56:39 +04:00
clean : {
2014-03-02 18:30:35 +04:00
built : {
2014-09-03 06:58:20 +04:00
src : [
'core/built/**' ,
2015-02-14 21:43:18 +03:00
'core/client/dist/**' ,
'core/client/public/assets/img/contributors/**' ,
'core/client/app/templates/-contributors.hbs'
2014-09-03 06:58:20 +04:00
]
2014-03-02 18:30:35 +04:00
} ,
2013-11-01 19:33:49 +04:00
release : {
2014-08-06 04:44:23 +04:00
src : [ '<%= paths.releaseBuild %>/**' ]
2013-10-15 07:39:52 +04:00
} ,
test : {
src : [ 'content/data/ghost-test.db' ]
2014-03-10 07:44:08 +04:00
} ,
tmp : {
src : [ '.tmp/**' ]
2015-05-18 00:41:49 +03:00
} ,
dependencies : {
src : [ 'node_modules/**' , 'core/client/bower_components/**' , 'core/client/node_modules/**' ]
2013-09-18 22:56:39 +04:00
}
} ,
2014-05-03 17:34:41 +04:00
// ### grunt-contrib-compress
// Zip up files for builds / releases
2013-06-25 15:43:15 +04:00
compress : {
2013-10-11 19:31:47 +04:00
release : {
options : {
archive : '<%= paths.releaseDist %>/Ghost-<%= pkg.version %>.zip'
} ,
expand : true ,
2013-11-01 19:33:49 +04:00
cwd : '<%= paths.releaseBuild %>/' ,
2013-10-11 19:31:47 +04:00
src : [ '**' ]
2013-06-10 17:52:04 +04:00
}
2013-07-02 00:58:47 +04:00
} ,
2014-05-27 18:37:44 +04:00
// ### grunt-update-submodules
// Grunt task to update git submodules
2014-09-10 08:06:24 +04:00
update _submodules : {
2014-05-27 18:37:44 +04:00
default : {
options : {
2014-07-01 03:26:08 +04:00
params : '--init'
2014-05-27 18:37:44 +04:00
}
}
2015-12-10 17:46:58 +03:00
} ,
uglify : {
prod : {
options : {
sourceMap : false
} ,
files : {
'core/shared/ghost-url.min.js' : 'core/shared/ghost-url.js'
}
}
2013-06-25 15:43:15 +04:00
}
2013-06-10 17:52:04 +04:00
} ;
2013-05-13 23:18:20 +04:00
2014-05-03 17:34:41 +04:00
// Load the configuration
2013-06-25 15:43:15 +04:00
grunt . initConfig ( cfg ) ;
2014-05-03 17:34:41 +04:00
// ## Utilities
//
// ### Spawn Casper.js
// Custom test runner for our Casper.js functional tests
// This really ought to be refactored into a separate grunt task module
2014-06-17 01:44:09 +04:00
grunt . registerTask ( 'spawnCasperJS' , function ( target ) {
2014-10-19 23:10:13 +04:00
target = _ . contains ( [ 'client' , 'setup' ] , target ) ? target + '/' : undefined ;
2014-06-17 01:44:09 +04:00
2013-08-27 01:28:41 +04:00
var done = this . async ( ) ,
options = [ 'host' , 'noPort' , 'port' , 'email' , 'password' ] ,
2013-10-19 04:13:07 +04:00
args = [ 'test' ]
2014-10-19 23:10:13 +04:00
. concat ( grunt . option ( 'target' ) || target || [ 'client/' ] )
2014-06-18 00:53:56 +04:00
. concat ( [ '--includes=base.js' , '--log-level=debug' , '--port=2369' ] ) ;
2013-08-27 01:28:41 +04:00
// Forward parameters from grunt to casperjs
_ . each ( options , function processOption ( option ) {
if ( grunt . option ( option ) ) {
args . push ( '--' + option + '=' + grunt . option ( option ) ) ;
}
} ) ;
2014-06-05 01:28:54 +04:00
if ( grunt . option ( 'fail-fast' ) ) {
args . push ( '--fail-fast' ) ;
}
2014-06-18 00:53:56 +04:00
// Show concise logs in Travis as ours are getting too long
if ( grunt . option ( 'concise' ) || process . env . TRAVIS ) {
2014-06-05 01:28:54 +04:00
args . push ( '--concise' ) ;
2014-06-18 00:53:56 +04:00
} else {
args . push ( '--verbose' ) ;
2014-06-05 01:28:54 +04:00
}
2013-08-27 01:28:41 +04:00
grunt . util . spawn ( {
cmd : 'casperjs' ,
args : args ,
opts : {
cwd : path . resolve ( 'core/test/functional' ) ,
stdio : 'inherit'
}
} , function ( error , result , code ) {
2014-02-27 06:44:09 +04:00
/*jshint unused:false*/
2013-08-27 01:28:41 +04:00
if ( error ) {
2015-06-12 21:37:23 +03:00
grunt . fail . fatal ( result . stderr ) ;
2013-08-27 01:28:41 +04:00
}
grunt . log . writeln ( result . stdout ) ;
done ( ) ;
} ) ;
} ) ;
2014-05-03 17:34:41 +04:00
// # Custom Tasks
// Ghost has a number of useful tasks that we use every day in development. Tasks marked as *Utility* are used
// by grunt to perform current actions, but isn't useful to developers.
//
// Skip ahead to the section on:
//
// * [Building assets](#building%20assets):
// `grunt init`, `grunt` & `grunt prod` or live reload with `grunt dev`
// * [Testing](#testing):
2015-04-18 15:20:03 +03:00
// `grunt validate`, the `grunt test-*` sub-tasks or generate a coverage report with `grunt coverage`.
2014-05-03 17:34:41 +04:00
// ### Help
// Run `grunt help` on the commandline to get a print out of the available tasks and details of
// what each one does along with any available options. This is an alias for `grunt --help`
grunt . registerTask ( 'help' ,
'Outputs help information if you type `grunt help` instead of `grunt --help`' ,
function ( ) {
console . log ( 'Type `grunt --help` to get the details of available grunt tasks, ' +
'or alternatively visit https://github.com/TryGhost/Ghost/wiki/Grunt-Toolkit' ) ;
} ) ;
2013-08-01 11:12:59 +04:00
2014-05-03 17:34:41 +04:00
// ### Documentation
// Run `grunt docs` to generate annotated source code using the documentation described in the code comments.
2014-05-05 19:26:19 +04:00
grunt . registerTask ( 'docs' , 'Generate Docs' , [ 'docker' ] ) ;
2013-08-01 11:12:59 +04:00
2015-05-28 18:16:09 +03:00
// Runun `grunt watch-docs` to setup livereload & watch whilst you're editing the docs
grunt . registerTask ( 'watch-docs' , function ( ) {
grunt . config . merge ( {
watch : {
docs : {
files : [ 'core/server/**/*' , 'index.js' , 'Gruntfile.js' , 'config.example.js' ] ,
tasks : [ 'docker' ] ,
options : {
livereload : true
}
}
}
} ) ;
grunt . task . run ( 'watch:docs' ) ;
} ) ;
2014-05-03 17:34:41 +04:00
// ## Testing
2013-08-01 11:12:59 +04:00
2014-05-03 17:34:41 +04:00
// Ghost has an extensive set of test suites. The following section documents the various types of tests
// and how to run them.
//
// TLDR; run `grunt validate`
2013-08-01 11:12:59 +04:00
2014-05-03 17:34:41 +04:00
// #### Set Test Env *(Utility Task)*
// Set the NODE_ENV to 'testing' unless the environment is already set to TRAVIS.
// This ensures that the tests get run under the correct environment, using the correct database, and
// that they work as expected. Trying to run tests with no ENV set will throw an error to do with `client`.
grunt . registerTask ( 'setTestEnv' ,
'Use "testing" Ghost config; unless we are running on travis (then show queries for debugging)' ,
function ( ) {
process . env . NODE _ENV = process . env . TRAVIS ? process . env . NODE _ENV : 'testing' ;
cfg . express . test . options . node _env = process . env . NODE _ENV ;
} ) ;
2013-08-01 11:12:59 +04:00
2014-07-17 18:33:21 +04:00
// #### Ensure Config *(Utility Task)*
2014-05-03 17:34:41 +04:00
// Make sure that we have a `config.js` file when running tests
// Ghost requires a `config.js` file to specify the database settings etc. Ghost comes with an example file:
// `config.example.js` which is copied and renamed to `config.js` by the bootstrap process
2014-07-17 18:33:21 +04:00
grunt . registerTask ( 'ensureConfig' , function ( ) {
2014-08-23 20:19:13 +04:00
var config = require ( './core/server/config' ) ,
2014-07-17 18:33:21 +04:00
done = this . async ( ) ;
2015-11-13 14:54:50 +03:00
if ( ! process . env . TEST _SUITE || process . env . TEST _SUITE !== 'client' ) {
config . load ( ) . then ( function ( ) {
done ( ) ;
} ) . catch ( function ( err ) {
grunt . fail . fatal ( err . stack ) ;
} ) ;
} else {
2014-05-03 17:34:41 +04:00
done ( ) ;
2015-11-13 14:54:50 +03:00
}
2013-08-01 11:12:59 +04:00
} ) ;
2014-07-15 23:52:44 +04:00
// #### Reset Database to "New" state *(Utility Task)*
// Drops all database tables and then runs the migration process to put the database
// in a "new" state.
grunt . registerTask ( 'cleanDatabase' , function ( ) {
var done = this . async ( ) ,
2014-08-14 01:58:12 +04:00
models = require ( './core/server/models' ) ,
2014-07-15 23:52:44 +04:00
migration = require ( './core/server/data/migration' ) ;
migration . reset ( ) . then ( function ( ) {
2014-08-14 01:58:12 +04:00
return models . init ( ) ;
} ) . then ( function ( ) {
2014-07-15 23:52:44 +04:00
return migration . init ( ) ;
} ) . then ( function ( ) {
done ( ) ;
} ) . catch ( function ( err ) {
grunt . fail . fatal ( err . stack ) ;
} ) ;
} ) ;
2014-12-24 16:51:16 +03:00
grunt . registerTask ( 'test' , function ( test ) {
if ( ! test ) {
2015-09-27 20:29:14 +03:00
grunt . fail . fatal ( 'No test provided. `grunt test` expects a filename. e.g.: `grunt test:unit/apps_spec.js`. Did you mean `npm test` or `grunt validate`?' ) ;
2014-12-24 16:51:16 +03:00
}
2015-01-25 23:27:00 +03:00
grunt . task . run ( 'test-setup' , 'shell:test:' + test ) ;
2014-12-24 16:51:16 +03:00
} ) ;
2014-05-03 17:34:41 +04:00
// ### Validate
// **Main testing task**
//
2014-07-01 16:35:50 +04:00
// `grunt validate` will build, lint and test your local Ghost codebase.
2014-05-03 17:34:41 +04:00
//
// `grunt validate` is one of the most important and useful grunt tasks that we have available to use. It
2014-07-01 16:35:50 +04:00
// manages the build of your environment and then calls `grunt test`
2014-05-03 17:34:41 +04:00
//
2014-07-01 16:35:50 +04:00
// `grunt validate` is called by `npm test` and is used by Travis.
2015-11-13 14:54:50 +03:00
grunt . registerTask ( 'validate' , 'Run tests and lint code' , function ( ) {
if ( process . env . TEST _SUITE === 'server' ) {
2015-12-04 20:45:32 +03:00
grunt . task . run ( [ 'init' , 'test-server' ] ) ;
2015-11-13 14:54:50 +03:00
} else if ( process . env . TEST _SUITE === 'client' ) {
2015-12-04 20:45:32 +03:00
grunt . task . run ( [ 'init' , 'test-client' ] ) ;
2015-11-13 14:54:50 +03:00
} else if ( process . env . TEST _SUITE === 'lint' ) {
2015-12-03 20:23:17 +03:00
grunt . task . run ( [ 'shell:ember:init' , 'shell:bower' , 'lint' ] ) ;
2015-11-13 14:54:50 +03:00
} else {
grunt . task . run ( [ 'validate-all' ] ) ;
}
} ) ;
grunt . registerTask ( 'validate-all' , 'Lint code and run all tests' ,
2015-04-22 23:39:35 +03:00
[ 'init' , 'lint' , 'test-all' ] ) ;
2014-07-01 16:35:50 +04:00
2015-01-25 23:27:00 +03:00
// ### Test-All
2014-07-01 16:35:50 +04:00
// **Main testing task**
//
2015-01-25 23:27:00 +03:00
// `grunt test-all` will lint and test your pre-built local Ghost codebase.
2014-07-01 16:35:50 +04:00
//
2015-11-13 14:54:50 +03:00
// `grunt test-all` runs all 6 test suites. See the individual sub tasks below for
2014-09-19 18:52:48 +04:00
// details of each of the test suites.
2014-07-01 16:35:50 +04:00
//
2015-11-13 14:54:50 +03:00
grunt . registerTask ( 'test-all' , 'Run tests for both server and client' ,
2015-12-04 20:45:32 +03:00
[ 'test-server' , 'test-client' ] ) ;
2015-11-13 14:54:50 +03:00
grunt . registerTask ( 'test-server' , 'Run server tests' ,
2015-12-04 20:45:32 +03:00
[ 'test-routes' , 'test-module' , 'test-unit' , 'test-integration' ] ) ;
2015-11-13 14:54:50 +03:00
grunt . registerTask ( 'test-client' , 'Run client tests' ,
2015-12-04 20:45:32 +03:00
[ 'test-ember' ] ) ;
2014-05-03 17:34:41 +04:00
2014-09-19 18:52:48 +04:00
// ### Lint
//
// `grunt lint` will run the linter and the code style checker so you can make sure your code is pretty
2015-01-25 23:27:00 +03:00
grunt . registerTask ( 'lint' , 'Run the code style checks and linter' ,
2015-05-17 15:33:41 +03:00
[ 'jshint' , 'jscs' , 'shell:csscomblint' ]
2015-01-25 23:27:00 +03:00
) ;
// ### test-setup *(utility)(
// `grunt test-setup` will run all the setup tasks required for running tests
grunt . registerTask ( 'test-setup' , 'Setup ready to run tests' ,
[ 'clean:test' , 'setTestEnv' , 'ensureConfig' ]
) ;
2014-09-19 18:52:48 +04:00
2014-05-03 17:34:41 +04:00
// ### Unit Tests *(sub task)*
// `grunt test-unit` will run just the unit tests
//
// Provided you already have a `config.js` file, you can run individual sections from
// [mochacli](#grunt-mocha-cli) by running:
//
// `NODE_ENV=testing grunt mochacli:section`
//
2014-06-17 01:44:09 +04:00
// If you need to run an individual unit test file, you can do so, providing you have mocha installed globally
// by using a command in the form:
2014-05-03 17:34:41 +04:00
//
// `NODE_ENV=testing mocha --timeout=15000 --ui=bdd --reporter=spec core/test/unit/config_spec.js`
//
2014-11-27 23:50:15 +03:00
// Unit tests are run with [mocha](http://mochajs.org/) using
2014-05-03 17:34:41 +04:00
// [should](https://github.com/visionmedia/should.js) to describe the tests in a highly readable style.
// Unit tests do **not** touch the database.
// A coverage report can be generated for these tests using the `grunt test-coverage` task.
grunt . registerTask ( 'test-unit' , 'Run unit tests (mocha)' ,
2015-01-25 23:27:00 +03:00
[ 'test-setup' , 'mochacli:unit' ]
) ;
2014-05-03 17:34:41 +04:00
// ### Integration tests *(sub task)*
// `grunt test-integration` will run just the integration tests
//
// Provided you already have a `config.js` file, you can run just the model integration tests by running:
//
// `NODE_ENV=testing grunt mochacli:model`
//
// Or just the api integration tests by running:
//
// `NODE_ENV=testing grunt mochacli:api`
//
2014-11-27 23:50:15 +03:00
// Integration tests are run with [mocha](http://mochajs.org/) using
2014-05-03 17:34:41 +04:00
// [should](https://github.com/visionmedia/should.js) to describe the tests in a highly readable style.
// Integration tests are different to the unit tests because they make requests to the database.
//
2014-07-20 18:11:02 +04:00
// If you need to run an individual integration test file you can do so, providing you have mocha installed
// globally, by using a command in the form (replace path to api_tags_spec.js with the test file you want to
// run):
//
2014-07-21 13:29:03 +04:00
// `NODE_ENV=testing mocha --timeout=15000 --ui=bdd --reporter=spec core/test/integration/api/api_tags_spec.js`
2014-07-20 18:11:02 +04:00
//
2014-05-03 17:34:41 +04:00
// Their purpose is to test that both the api and models behave as expected when the database layer is involved.
// These tests are run against sqlite3, mysql and pg on travis and ensure that differences between the databases
// don't cause bugs. At present, pg often fails and is not officially supported.
//
// A coverage report can be generated for these tests using the `grunt test-coverage` task.
grunt . registerTask ( 'test-integration' , 'Run integration tests (mocha + db access)' ,
2015-01-25 23:27:00 +03:00
[ 'test-setup' , 'mochacli:integration' ]
) ;
2014-05-03 17:34:41 +04:00
// ### Route tests *(sub task)*
// `grunt test-routes` will run just the route tests
//
// If you need to run an individual route test file, you can do so, providing you have a `config.js` file and
// mocha installed globally by using a command in the form:
//
2015-04-18 15:20:03 +03:00
// `NODE_ENV=testing mocha --timeout=15000 --ui=bdd --reporter=spec core/test/functional/routes/admin_spec.js`
2014-05-03 17:34:41 +04:00
//
2014-11-27 23:50:15 +03:00
// Route tests are run with [mocha](http://mochajs.org/) using
2014-05-03 17:34:41 +04:00
// [should](https://github.com/visionmedia/should.js) and [supertest](https://github.com/visionmedia/supertest)
// to describe and create the tests.
//
// Supertest enables us to describe requests that we want to make, and also describe the response we expect to
// receive back. It works directly with express, so we don't have to run a server to run the tests.
//
// The purpose of the route tests is to ensure that all of the routes (pages, and API requests) in Ghost
// are working as expected, including checking the headers and status codes received. It is very easy and
// quick to test many permutations of routes / urls in the system.
grunt . registerTask ( 'test-routes' , 'Run functional route tests (mocha)' ,
2015-01-25 23:27:00 +03:00
[ 'test-setup' , 'mochacli:routes' ]
) ;
2014-05-03 17:34:41 +04:00
2014-09-27 21:26:26 +04:00
// ### Module tests *(sub task)*
// `grunt test-module` will run just the module tests
//
// The purpose of the module tests is to ensure that Ghost can be used as an npm module and exposes all
// required methods to interact with it.
grunt . registerTask ( 'test-module' , 'Run functional module tests (mocha)' ,
2015-01-25 23:27:00 +03:00
[ 'test-setup' , 'mochacli:module' ]
) ;
2014-09-27 21:26:26 +04:00
2015-01-25 23:27:00 +03:00
// ### Ember unit tests *(sub task)*
2015-06-03 09:25:56 +03:00
// `grunt test-ember` will run just the ember unit tests
2015-05-18 00:41:49 +03:00
grunt . registerTask ( 'test-ember' , 'Run the ember unit tests' ,
[ 'test-setup' , 'shell:ember:test' ]
2014-07-15 23:52:44 +04:00
) ;
2014-05-03 17:34:41 +04:00
// ### Functional tests *(sub task)*
// `grunt test-functional` will run just the functional tests
//
// You can use the `--target` argument to run any individual test file, or the admin or frontend tests:
//
2014-07-30 04:11:02 +04:00
// `grunt test-functional --target=client/editor_test.js` - run just the editor tests
2014-05-03 17:34:41 +04:00
//
2014-07-30 04:11:02 +04:00
// `grunt test-functional --target=client/` - run all of the tests in the client directory
2014-05-03 17:34:41 +04:00
//
// Functional tests are run with [phantom.js](http://phantomjs.org/) and defined using the testing api from
// [casper.js](http://docs.casperjs.org/en/latest/testing.html).
//
// An express server is started with the testing environment set, and then a headless phantom.js browser is
// used to make requests to that server. The Casper.js API then allows us to describe the elements and
// interactions we expect to appear on the page.
//
// The purpose of the functional tests is to ensure that Ghost is working as is expected from a user perspective
// including buttons and other important interactions in the admin UI.
grunt . registerTask ( 'test-functional' , 'Run functional interface tests (CasperJS)' ,
2015-06-03 09:25:56 +03:00
[ 'test-setup' , 'shell:ember:dev' , 'cleanDatabase' , 'express:test' , 'spawnCasperJS' , 'express:test:stop' , 'test-functional-setup' ]
2015-01-25 23:27:00 +03:00
) ;
// ### Functional tests for the setup process
// `grunt test-functional-setup will run just the functional tests for the setup page.
//
// Setup only works with a brand new database, so it needs to run isolated from the rest of
// the functional tests.
grunt . registerTask ( 'test-functional-setup' , 'Run functional tests for setup' ,
[ 'test-setup' , 'cleanDatabase' , 'express:test' , 'spawnCasperJS:setup' , 'express:test:stop' ]
2014-05-03 17:34:41 +04:00
) ;
// ### Coverage
2015-04-18 15:20:03 +03:00
// `grunt coverage` will generate a report for the Unit Tests.
2014-05-03 17:34:41 +04:00
//
// This is not currently done as part of CI or any build, but is a tool we have available to keep an eye on how
// well the unit and integration tests are covering the code base.
// Ghost does not have a minimum coverage level - we're more interested in ensuring important and useful areas
// of the codebase are covered, than that the whole codebase is covered to a particular level.
//
// Key areas for coverage are: helpers and theme elements, apps / GDK, the api and model layers.
2015-04-18 15:20:03 +03:00
grunt . registerTask ( 'coverage' , 'Generate unit and integration (mocha) tests coverage report' ,
[ 'test-setup' , 'mocha_istanbul:coverage' ]
2015-01-25 23:27:00 +03:00
) ;
2014-05-03 17:34:41 +04:00
2015-07-28 17:02:00 +03:00
grunt . registerTask ( 'coverage-integration' , 'Generate unit and integration tests coverage report' ,
[ 'test-setup' , 'mocha_istanbul:coverage_integration' ]
) ;
2014-05-03 17:34:41 +04:00
// #### Master Warning *(Utility Task)*
// Warns git users not ot use the `master` branch in production.
// `master` is an unstable branch and shouldn't be used in production as you run the risk of ending up with a
// database in an unrecoverable state. Instead there is a branch called `stable` which is the equivalent of the
// release zip for git users.
2014-04-16 13:45:49 +04:00
grunt . registerTask ( 'master-warn' ,
'Outputs a warning to runners of grunt prod, that master shouldn\'t be used for live blogs' ,
function ( ) {
2015-05-19 18:51:53 +03:00
console . log ( '>' , chalk . red ( 'Always two there are, no more, no less. A master and a' ) ,
chalk . bold . red ( 'stable' ) + chalk . red ( '.' ) ) ;
console . log ( 'Use the' , chalk . bold ( 'stable' ) , 'branch for live blogs.' , chalk . bold ( 'Never' ) , 'master!' ) ;
2014-04-16 13:45:49 +04:00
} ) ;
2014-08-02 19:21:38 +04:00
// ### Build About Page *(Utility Task)*
// Builds the github contributors partial template used on the Settings/About page,
// and downloads the avatar for each of the users.
2015-02-14 21:43:18 +03:00
// Run by any task that compiles the ember assets or manually via `grunt buildAboutPage`.
2014-09-03 06:58:20 +04:00
// Only builds if the contributors template does not exist.
// To force a build regardless, supply the --force option.
// `grunt buildAboutPage --force`
2014-08-02 19:21:38 +04:00
grunt . registerTask ( 'buildAboutPage' , 'Compile assets for the About Ghost page' , function ( ) {
2014-09-03 06:58:20 +04:00
var done = this . async ( ) ,
2015-02-14 21:43:18 +03:00
templatePath = 'core/client/app/templates/-contributors.hbs' ,
imagePath = 'core/client/public/assets/img/contributors/' ,
2015-07-15 21:45:38 +03:00
timeSpan = moment ( ) . subtract ( 90 , 'days' ) . format ( 'YYYY-MM-DD' ) ,
2015-01-07 23:44:14 +03:00
oauthKey = process . env . GITHUB _OAUTH _KEY ;
2014-09-03 06:58:20 +04:00
if ( fs . existsSync ( templatePath ) && ! grunt . option ( 'force' ) ) {
grunt . log . writeln ( 'Contributors template already exists.' ) ;
2015-05-19 18:51:53 +03:00
grunt . log . writeln ( chalk . bold ( 'Skipped' ) ) ;
2014-09-03 06:58:20 +04:00
return done ( ) ;
}
2014-08-02 19:21:38 +04:00
grunt . verbose . writeln ( 'Downloading release and contributor information from GitHub' ) ;
2014-12-25 03:51:29 +03:00
return Promise . join (
Promise . promisify ( fs . mkdirs ) ( imagePath ) ,
getTopContribs ( {
user : 'tryghost' ,
repo : 'ghost' ,
2015-01-07 23:44:14 +03:00
oauthKey : oauthKey ,
2015-07-15 21:45:38 +03:00
sinceDate : timeSpan ,
2015-05-19 14:42:53 +03:00
count : 18 ,
2015-02-05 21:52:53 +03:00
retry : true
2014-12-25 03:51:29 +03:00
} )
) . then ( function ( results ) {
var contributors = results [ 1 ] ,
2015-05-19 14:42:53 +03:00
contributorTemplate = '<article>\n <a href="<%githubUrl%>" title="<%name%>">\n' +
' <img src="{{gh-path "admin" "/img/contributors"}}/<%name%>" alt="<%name%>" />\n' +
' </a>\n</article>' ,
2014-12-25 03:51:29 +03:00
downloadImagePromise = function ( url , name ) {
return new Promise ( function ( resolve , reject ) {
request ( url )
. pipe ( fs . createWriteStream ( imagePath + name ) )
. on ( 'close' , resolve )
. on ( 'error' , reject ) ;
} ) ;
} ;
2014-08-02 19:21:38 +04:00
grunt . verbose . writeln ( 'Creating contributors template.' ) ;
2014-09-03 06:58:20 +04:00
grunt . file . write ( templatePath ,
2014-09-10 08:06:24 +04:00
// Map contributors to the template.
2014-08-02 19:21:38 +04:00
_ . map ( contributors , function ( contributor ) {
return contributorTemplate
. replace ( /<%githubUrl%>/g , contributor . githubUrl )
. replace ( /<%name%>/g , contributor . name ) ;
} ) . join ( '\n' )
) ;
2014-12-25 03:51:29 +03:00
2014-08-02 19:21:38 +04:00
grunt . verbose . writeln ( 'Downloading images for top contributors' ) ;
return Promise . all ( _ . map ( contributors , function ( contributor ) {
2014-09-25 13:53:20 +04:00
return downloadImagePromise ( contributor . avatarUrl + '&s=60' , contributor . name ) ;
2014-08-02 19:21:38 +04:00
} ) ) ;
2014-12-25 03:51:29 +03:00
} ) . then ( done ) . catch ( function ( error ) {
2015-01-06 23:45:46 +03:00
grunt . log . error ( error ) ;
if ( error . http _status ) {
grunt . log . writeln ( 'GitHub API request returned status: ' + error . http _status ) ;
}
if ( error . ratelimit _limit ) {
grunt . log . writeln ( 'Rate limit data: limit: %d, remaining: %d, reset: %s' , error . ratelimit _limit , error . ratelimit _remaining , moment . unix ( error . ratelimit _reset ) . fromNow ( ) ) ;
}
2014-12-25 03:51:29 +03:00
done ( false ) ;
} ) ;
2014-08-02 19:21:38 +04:00
} ) ;
2015-02-14 21:43:18 +03:00
// ## Building assets
//
// Ghost's GitHub repository contains the un-built source code for Ghost. If you're looking for the already
// built release zips, you can get these from the [release page](https://github.com/TryGhost/Ghost/releases) on
// GitHub or from https://ghost.org/download. These zip files are created using the [grunt release](#release)
// task.
//
// If you want to work on Ghost core, or you want to use the source files from GitHub, then you have to build
// the Ghost assets in order to make them work.
//
// There are a number of grunt tasks available to help with this. Firstly after fetching an updated version of
// the Ghost codebase, after running `npm install`, you will need to run [grunt init](#init%20assets).
//
// For production blogs you will need to run [grunt prod](#production%20assets).
//
// For updating assets during development, the tasks [grunt](#default%20asset%20build) and
// [grunt dev](#live%20reload) are available.
2014-05-03 17:34:41 +04:00
// ### Init assets
// `grunt init` - will run an initial asset build for you
//
// Grunt init runs `bower install` as well as the standard asset build tasks which occur when you run just
// `grunt`. This fetches the latest client side dependencies, and moves them into their proper homes.
//
// This task is very important, and should always be run and when fetching down an updated code base just after
// running `npm install`.
//
// `bower` does have some quirks, such as not running as root. If you have problems please try running
// `grunt init --verbose` to see if there are any errors.
grunt . registerTask ( 'init' , 'Prepare the project for development' ,
2015-03-08 20:09:57 +03:00
[ 'shell:ember:init' , 'shell:bower' , 'update_submodules' , 'assets' , 'default' ] ) ;
2014-05-03 17:34:41 +04:00
2015-02-14 21:43:18 +03:00
// ### Basic Asset Building
// Builds and moves necessary client assets. Prod additionally builds the ember app.
grunt . registerTask ( 'assets' , 'Basic asset building & moving' ,
2015-08-18 16:08:52 +03:00
[ 'clean:tmp' , 'buildAboutPage' ] ) ;
2014-05-03 17:34:41 +04:00
// ### Default asset build
// `grunt` - default grunt task
//
2015-02-14 21:43:18 +03:00
// Build assets and dev version of the admin app.
2014-05-03 17:34:41 +04:00
grunt . registerTask ( 'default' , 'Build JS & templates for development' ,
2015-03-08 20:09:57 +03:00
[ 'shell:ember:dev' ] ) ;
2015-02-14 21:43:18 +03:00
// ### Production assets
// `grunt prod` - will build the minified assets used in production.
//
// It is otherwise the same as running `grunt`, but is only used when running Ghost in the `production` env.
grunt . registerTask ( 'prod' , 'Build JS & templates for production' ,
2015-12-10 17:46:58 +03:00
[ 'shell:ember:prod' , 'uglify:prod' , 'master-warn' ] ) ;
2014-05-03 17:34:41 +04:00
// ### Live reload
// `grunt dev` - build assets on the fly whilst developing
//
// If you want Ghost to live reload for you whilst you're developing, you can do this by running `grunt dev`.
// This works hand-in-hand with the [livereload](http://livereload.com/) chrome extension.
//
// `grunt dev` manages starting an express server and restarting the server whenever core files change (which
// require a server restart for the changes to take effect) and also manage reloading the browser whenever
// frontend code changes.
//
// Note that the current implementation of watch only works with casper, not other themes.
grunt . registerTask ( 'dev' , 'Dev Mode; watch files and restart server on changes' ,
2015-03-08 20:09:57 +03:00
[ 'bgShell:ember' , 'express:dev' , 'watch' ] ) ;
2014-05-03 17:34:41 +04:00
// ### Release
// Run `grunt release` to create a Ghost release zip file.
// Uses the files specified by `.npmignore` to know what should and should not be included.
// Runs the asset generation tasks for both development and production so that the release can be used in
// either environment, and packages all the files up into a zip.
grunt . registerTask ( 'release' ,
'Release task - creates a final built zip\n' +
2014-07-01 03:26:08 +04:00
' - Do our standard build steps \n' +
2014-05-03 17:34:41 +04:00
' - Copy files to release-folder/#/#{version} directory\n' +
2014-05-05 19:26:19 +04:00
' - Clean out unnecessary files (travis, .git*, etc)\n' +
2014-05-03 17:34:41 +04:00
' - Zip files in release-folder to dist-folder/#{version} directory' ,
2015-11-24 00:20:59 +03:00
function ( ) {
grunt . config . set ( 'copy.release' , {
expand : true ,
// #### Build File Patterns
// A list of files and patterns to include when creating a release zip.
// This is read from the `.npmignore` file and all patterns are inverted as the `.npmignore`
// file defines what to ignore, whereas we want to define what to include.
src : fs . readFileSync ( '.npmignore' , 'utf8' ) . split ( '\n' ) . filter ( Boolean ) . map ( function ( pattern ) {
return pattern [ 0 ] === '!' ? pattern . substr ( 1 ) : '!' + pattern ;
} ) ,
dest : '<%= paths.releaseBuild %>/'
} ) ;
2015-12-16 00:32:31 +03:00
grunt . task . run ( [ 'init' , 'prod' , 'clean:release' , 'shell:dedupe' , 'shell:shrinkwrap' , 'copy:release' , 'compress:release' ] ) ;
2015-11-24 00:20:59 +03:00
}
) ;
2013-06-25 15:43:15 +04:00
} ;
2014-06-02 18:55:49 +04:00
module . exports = configureGrunt ;