Wordpress/Gruntfile.js
Aaron Jorbin 8ad4a72960 Build/Test Tools: Run prerelease as the precommit task for configuration file changes
Whenever package.json or Gruntfile.js is updated, we should assume that it affects everything and run the full monty of tasks.

Fixes #36528.
Props iseulde.



git-svn-id: https://develop.svn.wordpress.org/trunk@37650 602fd350-edb4-49c9-b593-d223f7449a82
2016-06-07 03:32:22 +00:00

838 lines
20 KiB
JavaScript

/* jshint node:true */
module.exports = function(grunt) {
var path = require('path'),
fs = require( 'fs' ),
SOURCE_DIR = 'src/',
BUILD_DIR = 'build/',
autoprefixer = require('autoprefixer'),
mediaConfig = {},
mediaBuilds = ['audiovideo', 'grid', 'models', 'views'];
// Load tasks.
require('matchdep').filterDev(['grunt-*', '!grunt-legacy-util']).forEach( grunt.loadNpmTasks );
// Load legacy utils
grunt.util = require('grunt-legacy-util');
mediaBuilds.forEach( function ( build ) {
var path = SOURCE_DIR + 'wp-includes/js/media';
mediaConfig[ build ] = { files : {} };
mediaConfig[ build ].files[ path + '-' + build + '.js' ] = [ path + '/' + build + '.manifest.js' ];
} );
// Project configuration.
grunt.initConfig({
postcss: {
options: {
processors: [
autoprefixer({
browsers: [
'Android >= 2.1',
'Chrome >= 21',
'Edge >= 12',
'Explorer >= 7',
'Firefox >= 17',
'Opera >= 12.1',
'Safari >= 6.0'
],
cascade: false
})
]
},
core: {
expand: true,
cwd: SOURCE_DIR,
dest: SOURCE_DIR,
src: [
'wp-admin/css/*.css',
'wp-includes/css/*.css'
]
},
colors: {
expand: true,
cwd: BUILD_DIR,
dest: BUILD_DIR,
src: [
'wp-admin/css/colors/*/colors.css'
]
}
},
clean: {
all: [BUILD_DIR],
dynamic: {
dot: true,
expand: true,
cwd: BUILD_DIR,
src: []
},
tinymce: ['<%= concat.tinymce.dest %>'],
qunit: ['tests/qunit/compiled.html']
},
copy: {
files: {
files: [
{
dot: true,
expand: true,
cwd: SOURCE_DIR,
src: [
'**',
'!wp-includes/js/media/**',
'!**/.{svn,git}/**', // Ignore version control directories.
// Ignore unminified versions of external libs we don't ship:
'!wp-includes/js/backbone.js',
'!wp-includes/js/underscore.js',
'!wp-includes/js/jquery/jquery.masonry.js',
'!wp-includes/js/jquery/ui/*.js',
'!wp-includes/js/tinymce/tinymce.js',
'!wp-includes/version.php' // Exclude version.php
],
dest: BUILD_DIR
},
{
src: 'wp-config-sample.php',
dest: BUILD_DIR
}
]
},
'wp-admin-css-compat-rtl': {
options: {
processContent: function( src ) {
return src.replace( /\.css/g, '-rtl.css' );
}
},
src: SOURCE_DIR + 'wp-admin/css/wp-admin.css',
dest: BUILD_DIR + 'wp-admin/css/wp-admin-rtl.css'
},
'wp-admin-css-compat-min': {
options: {
processContent: function( src ) {
return src.replace( /\.css/g, '.min.css' );
}
},
files: [
{
src: SOURCE_DIR + 'wp-admin/css/wp-admin.css',
dest: BUILD_DIR + 'wp-admin/css/wp-admin.min.css'
},
{
src: BUILD_DIR + 'wp-admin/css/wp-admin-rtl.css',
dest: BUILD_DIR + 'wp-admin/css/wp-admin-rtl.min.css'
}
]
},
version: {
options: {
processContent: function( src ) {
return src.replace( /^\$wp_version = '(.+?)';/m, function( str, version ) {
version = version.replace( /-src$/, '' );
// If the version includes an SVN commit (-12345), it's not a released alpha/beta. Append a timestamp.
version = version.replace( /-[\d]{5}$/, '-' + grunt.template.today( 'yyyymmdd.HHMMss' ) );
/* jshint quotmark: true */
return "$wp_version = '" + version + "';";
});
}
},
src: SOURCE_DIR + 'wp-includes/version.php',
dest: BUILD_DIR + 'wp-includes/version.php'
},
dynamic: {
dot: true,
expand: true,
cwd: SOURCE_DIR,
dest: BUILD_DIR,
src: []
},
qunit: {
src: 'tests/qunit/index.html',
dest: 'tests/qunit/compiled.html',
options: {
processContent: function( src ) {
return src.replace( /(\".+?\/)src(\/.+?)(?:.min)?(.js\")/g , function( match, $1, $2, $3 ) {
// Don't add `.min` to files that don't have it.
return $1 + 'build' + $2 + ( /jquery$/.test( $2 ) ? '' : '.min' ) + $3;
} );
}
}
}
},
browserify: mediaConfig,
sass: {
colors: {
expand: true,
cwd: SOURCE_DIR,
dest: BUILD_DIR,
ext: '.css',
src: ['wp-admin/css/colors/*/colors.scss'],
options: {
outputStyle: 'expanded'
}
}
},
cssmin: {
options: {
compatibility: 'ie7'
},
core: {
expand: true,
cwd: SOURCE_DIR,
dest: BUILD_DIR,
ext: '.min.css',
src: [
'wp-admin/css/*.css',
'!wp-admin/css/wp-admin*.css',
'wp-includes/css/*.css',
'wp-includes/js/mediaelement/wp-mediaelement.css'
]
},
rtl: {
expand: true,
cwd: BUILD_DIR,
dest: BUILD_DIR,
ext: '.min.css',
src: [
'wp-admin/css/*-rtl.css',
'!wp-admin/css/wp-admin*.css',
'wp-includes/css/*-rtl.css'
]
},
colors: {
expand: true,
cwd: BUILD_DIR,
dest: BUILD_DIR,
ext: '.min.css',
src: [
'wp-admin/css/colors/*/*.css'
]
}
},
rtlcss: {
options: {
// rtlcss options
opts: {
clean: false,
processUrls: { atrule: true, decl: false },
stringMap: [
{
name: 'import-rtl-stylesheet',
priority: 10,
exclusive: true,
search: [ '.css' ],
replace: [ '-rtl.css' ],
options: {
scope: 'url',
ignoreCase: false
}
}
]
},
saveUnmodified: false,
plugins: [
{
name: 'swap-dashicons-left-right-arrows',
priority: 10,
directives: {
control: {},
value: []
},
processors: [
{
expr: /content/im,
action: function( prop, value ) {
if ( value === '"\\f141"' ) { // dashicons-arrow-left
value = '"\\f139"';
} else if ( value === '"\\f340"' ) { // dashicons-arrow-left-alt
value = '"\\f344"';
} else if ( value === '"\\f341"' ) { // dashicons-arrow-left-alt2
value = '"\\f345"';
} else if ( value === '"\\f139"' ) { // dashicons-arrow-right
value = '"\\f141"';
} else if ( value === '"\\f344"' ) { // dashicons-arrow-right-alt
value = '"\\f340"';
} else if ( value === '"\\f345"' ) { // dashicons-arrow-right-alt2
value = '"\\f341"';
}
return { prop: prop, value: value };
}
}
]
}
]
},
core: {
expand: true,
cwd: SOURCE_DIR,
dest: BUILD_DIR,
ext: '-rtl.css',
src: [
'wp-admin/css/*.css',
'wp-includes/css/*.css',
// Exceptions
'!wp-includes/css/dashicons.css',
'!wp-includes/css/wp-embed-template.css',
'!wp-includes/css/wp-embed-template-ie.css'
]
},
colors: {
expand: true,
cwd: BUILD_DIR,
dest: BUILD_DIR,
ext: '-rtl.css',
src: [
'wp-admin/css/colors/*/colors.css'
]
},
dynamic: {
expand: true,
cwd: SOURCE_DIR,
dest: BUILD_DIR,
ext: '-rtl.css',
src: []
}
},
jshint: {
options: grunt.file.readJSON('.jshintrc'),
grunt: {
src: ['Gruntfile.js']
},
tests: {
src: [
'tests/qunit/**/*.js',
'!tests/qunit/vendor/*',
'!tests/qunit/editor/**'
],
options: grunt.file.readJSON('tests/qunit/.jshintrc')
},
themes: {
expand: true,
cwd: SOURCE_DIR + 'wp-content/themes',
src: [
'twenty*/**/*.js',
'!twenty{eleven,twelve,thirteen}/**',
// Third party scripts
'!twenty{fourteen,fifteen,sixteen}/js/html5.js'
]
},
media: {
options: {
browserify: true
},
src: [
SOURCE_DIR + 'wp-includes/js/media/**/*.js'
]
},
core: {
expand: true,
cwd: SOURCE_DIR,
src: [
'wp-admin/js/*.js',
'wp-includes/js/*.js',
// Built scripts.
'!wp-includes/js/media-*',
// WordPress scripts inside directories
'wp-includes/js/jquery/jquery.table-hotkeys.js',
'wp-includes/js/mediaelement/wp-mediaelement.js',
'wp-includes/js/mediaelement/wp-playlist.js',
'wp-includes/js/plupload/handlers.js',
'wp-includes/js/plupload/wp-plupload.js',
'wp-includes/js/tinymce/plugins/wordpress/plugin.js',
'wp-includes/js/tinymce/plugins/wp*/plugin.js',
// Third party scripts
'!wp-admin/js/farbtastic.js',
'!wp-includes/js/backbone*.js',
'!wp-includes/js/swfobject.js',
'!wp-includes/js/underscore*.js',
'!wp-includes/js/colorpicker.js',
'!wp-includes/js/hoverIntent.js',
'!wp-includes/js/json2.js',
'!wp-includes/js/tw-sack.js',
'!wp-includes/js/twemoji.js',
'!**/*.min.js'
],
// Remove once other JSHint errors are resolved
options: {
curly: false,
eqeqeq: false
},
// Limit JSHint's run to a single specified file:
//
// grunt jshint:core --file=filename.js
//
// Optionally, include the file path:
//
// grunt jshint:core --file=path/to/filename.js
//
filter: function( filepath ) {
var index, file = grunt.option( 'file' );
// Don't filter when no target file is specified
if ( ! file ) {
return true;
}
// Normalize filepath for Windows
filepath = filepath.replace( /\\/g, '/' );
index = filepath.lastIndexOf( '/' + file );
// Match only the filename passed from cli
if ( filepath === file || ( -1 !== index && index === filepath.length - ( file.length + 1 ) ) ) {
return true;
}
return false;
}
},
plugins: {
expand: true,
cwd: SOURCE_DIR + 'wp-content/plugins',
src: [
'**/*.js',
'!**/*.min.js'
],
// Limit JSHint's run to a single specified plugin directory:
//
// grunt jshint:plugins --dir=foldername
//
filter: function( dirpath ) {
var index, dir = grunt.option( 'dir' );
// Don't filter when no target folder is specified
if ( ! dir ) {
return true;
}
dirpath = dirpath.replace( /\\/g, '/' );
index = dirpath.lastIndexOf( '/' + dir );
// Match only the folder name passed from cli
if ( -1 !== index ) {
return true;
}
return false;
}
}
},
qunit: {
files: [
'tests/qunit/**/*.html',
'!tests/qunit/editor/**'
]
},
phpunit: {
'default': {
cmd: 'phpunit',
args: ['-c', 'phpunit.xml.dist']
},
ajax: {
cmd: 'phpunit',
args: ['-c', 'phpunit.xml.dist', '--group', 'ajax']
},
multisite: {
cmd: 'phpunit',
args: ['-c', 'tests/phpunit/multisite.xml']
},
'external-http': {
cmd: 'phpunit',
args: ['-c', 'phpunit.xml.dist', '--group', 'external-http']
}
},
uglify: {
options: {
ASCIIOnly: true
},
core: {
expand: true,
cwd: SOURCE_DIR,
dest: BUILD_DIR,
ext: '.min.js',
src: [
'wp-admin/js/*.js',
'wp-includes/js/*.js',
'wp-includes/js/mediaelement/wp-mediaelement.js',
'wp-includes/js/mediaelement/wp-playlist.js',
'wp-includes/js/plupload/handlers.js',
'wp-includes/js/plupload/wp-plupload.js',
'wp-includes/js/tinymce/plugins/wordpress/plugin.js',
'wp-includes/js/tinymce/plugins/wp*/plugin.js',
// Exceptions
'!wp-admin/js/bookmarklet.*', // Minified and updated in /src with the precommit task. See uglify:bookmarklet.
'!wp-admin/js/custom-header.js', // Why? We should minify this.
'!wp-admin/js/farbtastic.js',
'!wp-admin/js/iris.min.js',
'!wp-includes/js/backbone.*',
'!wp-includes/js/masonry.min.js',
'!wp-includes/js/swfobject.js',
'!wp-includes/js/underscore.*',
'!wp-includes/js/zxcvbn.min.js',
'!wp-includes/js/wp-embed.js' // We have extra options for this, see uglify:embed
]
},
embed: {
options: {
compress: {
conditionals: false
}
},
expand: true,
cwd: SOURCE_DIR,
dest: BUILD_DIR,
ext: '.min.js',
src: ['wp-includes/js/wp-embed.js']
},
media: {
expand: true,
cwd: SOURCE_DIR,
dest: BUILD_DIR,
ext: '.min.js',
src: [
'wp-includes/js/media-audiovideo.js',
'wp-includes/js/media-grid.js',
'wp-includes/js/media-models.js',
'wp-includes/js/media-views.js'
]
},
jqueryui: {
options: {
// Preserve comments that start with a bang.
preserveComments: /^!/
},
expand: true,
cwd: SOURCE_DIR,
dest: BUILD_DIR,
ext: '.min.js',
src: ['wp-includes/js/jquery/ui/*.js']
},
bookmarklet: {
options: {
compress: {
negate_iife: false
}
},
src: SOURCE_DIR + 'wp-admin/js/bookmarklet.js',
dest: SOURCE_DIR + 'wp-admin/js/bookmarklet.min.js'
}
},
concat: {
tinymce: {
options: {
separator: '\n',
process: function( src, filepath ) {
return '// Source: ' + filepath.replace( BUILD_DIR, '' ) + '\n' + src;
}
},
src: [
BUILD_DIR + 'wp-includes/js/tinymce/tinymce.min.js',
BUILD_DIR + 'wp-includes/js/tinymce/themes/modern/theme.min.js',
BUILD_DIR + 'wp-includes/js/tinymce/plugins/*/plugin.min.js'
],
dest: BUILD_DIR + 'wp-includes/js/tinymce/wp-tinymce.js'
},
emoji: {
options: {
separator: '\n',
process: function( src, filepath ) {
return '// Source: ' + filepath.replace( BUILD_DIR, '' ) + '\n' + src;
}
},
src: [
BUILD_DIR + 'wp-includes/js/twemoji.min.js',
BUILD_DIR + 'wp-includes/js/wp-emoji.min.js'
],
dest: BUILD_DIR + 'wp-includes/js/wp-emoji-release.min.js'
}
},
compress: {
tinymce: {
options: {
mode: 'gzip',
level: 9
},
src: '<%= concat.tinymce.dest %>',
dest: BUILD_DIR + 'wp-includes/js/tinymce/wp-tinymce.js.gz'
}
},
jsvalidate:{
options: {
globals: {},
esprimaOptions:{},
verbose: false
},
build: {
files: {
src: [
BUILD_DIR + 'wp-{admin,includes}/**/*.js',
BUILD_DIR + 'wp-content/themes/twenty*/**/*.js'
]
}
}
},
imagemin: {
core: {
expand: true,
cwd: SOURCE_DIR,
src: [
'wp-{admin,includes}/images/**/*.{png,jpg,gif,jpeg}',
'wp-includes/js/tinymce/skins/wordpress/images/*.{png,jpg,gif,jpeg}'
],
dest: SOURCE_DIR
}
},
includes: {
emoji: {
src: BUILD_DIR + 'wp-includes/formatting.php',
dest: '.'
},
embed: {
src: BUILD_DIR + 'wp-includes/embed.php',
dest: '.'
}
},
_watch: {
all: {
files: [
SOURCE_DIR + '**',
'!' + SOURCE_DIR + 'wp-includes/js/media/**',
// Ignore version control directories.
'!' + SOURCE_DIR + '**/.{svn,git}/**'
],
tasks: ['clean:dynamic', 'copy:dynamic'],
options: {
dot: true,
spawn: false,
interval: 2000
}
},
config: {
files: 'Gruntfile.js'
},
colors: {
files: [SOURCE_DIR + 'wp-admin/css/colors/**'],
tasks: ['sass:colors']
},
rtl: {
files: [
SOURCE_DIR + 'wp-admin/css/*.css',
SOURCE_DIR + 'wp-includes/css/*.css'
],
tasks: ['rtlcss:dynamic'],
options: {
spawn: false,
interval: 2000
}
},
test: {
files: [
'tests/qunit/**',
'!tests/qunit/editor/**'
],
tasks: ['qunit']
}
}
});
// Register tasks.
// RTL task.
grunt.registerTask('rtl', ['rtlcss:core', 'rtlcss:colors']);
// Color schemes task.
grunt.registerTask('colors', ['sass:colors', 'postcss:colors']);
// JSHint task.
grunt.registerTask( 'jshint:corejs', [
'jshint:grunt',
'jshint:tests',
'jshint:themes',
'jshint:core',
'jshint:media'
] );
grunt.renameTask( 'watch', '_watch' );
grunt.registerTask( 'watch', function() {
if ( ! this.args.length || this.args.indexOf( 'browserify' ) > -1 ) {
grunt.config( 'browserify.options', {
browserifyOptions: {
debug: true
},
watch: true
} );
grunt.task.run( 'browserify' );
}
grunt.task.run( '_' + this.nameArgs );
} );
grunt.registerTask( 'precommit:image', [
'imagemin:core'
] );
grunt.registerTask( 'precommit:js', [
'browserify',
'jshint:corejs',
'uglify:bookmarklet',
'qunit:compiled'
] );
grunt.registerTask( 'precommit:css', [
'postcss:core'
] );
grunt.registerTask( 'precommit:php', [
'phpunit'
] );
grunt.registerTask( 'precommit', 'Runs test and build tasks in preparation for a commit', function() {
var done = this.async();
var map = {
svn: 'svn status --ignore-externals',
git: 'git status --short'
};
find( [
__dirname + '/.svn',
__dirname + '/.git',
path.dirname( __dirname ) + '/.svn'
] );
function find( set ) {
var dir;
if ( set.length ) {
fs.stat( dir = set.shift(), function( error ) {
error ? find( set ) : run( path.basename( dir ).substr( 1 ) );
} );
} else {
grunt.fatal( 'This WordPress install is not under version control.' );
}
}
function run( type ) {
var command = map[ type ].split( ' ' );
grunt.util.spawn( {
cmd: command.shift(),
args: command
}, function( error, result, code ) {
var taskList = [];
if ( code !== 0 ) {
grunt.fatal( 'The `' + map[ type ] + '` command returned a non-zero exit code.', code );
}
if ( [ 'package.json', 'Gruntfile.js' ].some( function( path ) {
return ( result.stdout + '\n' ).indexOf( ' ' + path + '\n' ) !== -1;
} ) ) {
grunt.log.writeln( 'Configuration files modified. Running `prerelease`.' );
taskList.push( 'prerelease' );
} else {
if ( [ 'png', 'jpg', 'gif', 'jpeg' ].some( function( extension ) {
return ( result.stdout + '\n' ).indexOf( '.' + extension + '\n' ) !== -1;
} ) ) {
grunt.log.writeln( 'Image files modified. Minifying.' );
taskList.push( 'precommit:image' );
}
[ 'js', 'css', 'php' ].forEach( function( extension ) {
if ( ( result.stdout + '\n' ).indexOf( '.' + extension + '\n' ) !== -1 ) {
grunt.log.writeln( extension.toUpperCase() + ' files modified. ' + extension.toUpperCase() + ' tests will be run.' );
taskList.push( 'precommit:' + extension );
}
} );
}
grunt.task.run( taskList );
done();
} );
}
} );
grunt.registerTask( 'copy:all', [
'copy:files',
'copy:wp-admin-css-compat-rtl',
'copy:wp-admin-css-compat-min',
'copy:version'
] );
grunt.registerTask( 'build', [
'clean:all',
'copy:all',
'cssmin:core',
'colors',
'rtl',
'cssmin:rtl',
'cssmin:colors',
'uglify:core',
'uglify:embed',
'uglify:jqueryui',
'concat:tinymce',
'compress:tinymce',
'clean:tinymce',
'concat:emoji',
'includes:emoji',
'includes:embed',
'jsvalidate:build'
] );
grunt.registerTask( 'prerelease', [
'precommit:php',
'precommit:js',
'precommit:css',
'precommit:image'
] );
// Testing tasks.
grunt.registerMultiTask('phpunit', 'Runs PHPUnit tests, including the ajax, external-http, and multisite tests.', function() {
grunt.util.spawn({
cmd: this.data.cmd,
args: this.data.args,
opts: {stdio: 'inherit'}
}, this.async());
});
grunt.registerTask('qunit:compiled', 'Runs QUnit tests on compiled as well as uncompiled scripts.',
['build', 'copy:qunit', 'qunit']);
grunt.registerTask('test', 'Runs all QUnit and PHPUnit tasks.', ['qunit:compiled', 'phpunit']);
// Travis CI tasks.
grunt.registerTask('travis:js', 'Runs Javascript Travis CI tasks.', [ 'jshint:corejs', 'qunit:compiled' ]);
grunt.registerTask('travis:phpunit', 'Runs PHPUnit Travis CI tasks.', 'phpunit');
// Patch task.
grunt.renameTask('patch_wordpress', 'patch');
// Default task.
grunt.registerTask('default', ['build']);
/*
* Automatically updates the `:dynamic` configurations
* so that only the changed files are updated.
*/
grunt.event.on('watch', function( action, filepath, target ) {
var src;
if ( [ 'all', 'rtl', 'browserify' ].indexOf( target ) === -1 ) {
return;
}
src = [ path.relative( SOURCE_DIR, filepath ) ];
if ( action === 'deleted' ) {
grunt.config( [ 'clean', 'dynamic', 'src' ], src );
} else {
grunt.config( [ 'copy', 'dynamic', 'src' ], src );
if ( target === 'rtl' ) {
grunt.config( [ 'rtlcss', 'dynamic', 'src' ], src );
}
}
});
};