diff --git a/.eslintignore b/.eslintignore index 4cd1606..ff24424 100644 --- a/.eslintignore +++ b/.eslintignore @@ -1,3 +1,4 @@ **/*.min.js **/node_modules/** **/vendor/** +**/dist/** diff --git a/.eslintrc b/.eslintrc index d2661d1..5c9d1ad 100644 --- a/.eslintrc +++ b/.eslintrc @@ -1,13 +1,19 @@ { "env": { - "browser": true + "browser": true, + "es6": true, + "node": true + }, + "parserOptions": { + "sourceType": "module" }, "globals": { "_": false, "Backbone": false, - "jQuery": false, + "jQuery": true, "wp": false }, + "extends": "wordpress", "rules": { "accessor-pairs": [2], "block-scoped-var": [2], diff --git a/.gitignore b/.gitignore index bdfda97..7b2de3f 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,3 @@ codecept.phar +node_modules +npm-debug.log diff --git a/.jshintrc b/.jshintrc index 0a082dd..24e678a 100644 --- a/.jshintrc +++ b/.jshintrc @@ -3,7 +3,7 @@ "curly": true, "eqeqeq": true, "eqnull": true, - "es3": true, + "esversion": 6, "expr": true, "immed": true, "noarg": true, @@ -15,6 +15,7 @@ "unused": true, "browser": true, + "node": true, "globals": { "_": false, diff --git a/build-workflows.sh b/build-workflows.sh new file mode 100755 index 0000000..0de1f98 --- /dev/null +++ b/build-workflows.sh @@ -0,0 +1,7 @@ +#!/bin/bash + +set -e + +rm -rf ./workflows/dist +./node_modules/.bin/babel ./workflows/src --out-dir ./workflows/dist +mv ./workflows/dist/gulpfile.babel.js ./workflows/dist/gulpfile.js diff --git a/package.json b/package.json index 2655d44..4952d9a 100644 --- a/package.json +++ b/package.json @@ -2,10 +2,10 @@ "name": "wp-dev-lib", "version": "1.0.0", "description": "Common code used during development of WordPress plugins and themes", - "scripts": { - "test": "echo \"Error: no test specified\" && exit 1" - }, "repository": "xwp/wp-dev-lib", + "engines": { + "node": ">=6.11.x" + }, "keywords": [ "wordpress", "plugins", @@ -13,7 +13,91 @@ "development", "tools" ], - "author": "Weston Ruter (https://xwp.co)", + "author": { + "name": "Weston Ruter", + "email": "weston.ruter@xwp.co", + "url": "https://www.xwp.co" + }, + "contributors": [ + { + "name": "Piotr Delawski", + "email": "piotr.delawski@xwp.co" + }, + { + "name": "Mike Crantea", + "email": "mike.crantea@xwp.co" + }, + { + "name": "Justin Kopepasah", + "email": "justin.kopepasah@xwp.co", + "url": "https://kopepasah.com" + } + ], "license": "MIT", - "homepage": "https://github.com/xwp/wp-dev-lib#readme" + "homepage": "https://github.com/xwp/wp-dev-lib#readme", + "scripts": { + "build:self": "./build-workflows.sh", + "test": "echo \"Error: no test specified\" && exit 1" + }, + "browserslist": [ + "last 2 Chrome versions", + "last 2 Firefox versions", + "last 2 Safari versions", + "last 2 Edge versions", + "last 2 Opera versions", + "last 2 iOS versions", + "last 1 Android version", + "last 1 ChromeAndroid version", + "ie 11", + "> 1%" + ], + "babel": { + "presets": [ + [ + "env" + ] + ] + }, + "stylelint": { + "extends": "stylelint-config-wordpress/scss" + }, + "dependencies": { + "autoprefixer": "^7.1.2", + "babel-cli": "^6.24.1", + "babel-core": "^6.25.0", + "babel-loader": "^7.1.1", + "babel-preset-env": "^1.6.0", + "del": "^3.0.0", + "eslint": "^4.3.0", + "eslint-config-wordpress": "^2.0.0", + "eslint-loader": "^1.9.0", + "eslint-plugin-compat": "^1.0.4", + "gulp": "gulpjs/gulp.git#4.0", + "gulp-cached": "^1.1.1", + "gulp-eslint": "^4.0.0", + "gulp-if": "^2.0.2", + "gulp-imagemin": "^3.3.0", + "gulp-plumber": "^1.1.0", + "gulp-postcss": "^7.0.0", + "gulp-progeny": "^0.4.0", + "gulp-sass": "^3.1.0", + "gulp-sourcemaps": "^2.6.0", + "gulp-watch": "^4.3.11", + "lodash": "^4.17.4", + "postcss": "^6.0.8", + "postcss-assets": "^4.2.0", + "postcss-cssnext": "^3.0.2", + "postcss-pxtorem": "^4.0.1", + "postcss-reporter": "^4.0.0", + "postcss-scss": "^1.0.2", + "progress-bar-webpack-plugin": "^1.10.0", + "require-dir": "^0.3.2", + "stylelint": "^8.0.0", + "stylelint-config-wordpress": "^12.0.0", + "validate-node-version": "^1.1.1", + "webpack": "^3.3.0", + "webpack-config-utils": "^2.3.0", + "webpack-stream": "^3.2.0", + "yargs": "^8.0.2" + } } diff --git a/workflows/dist/gulpfile.js b/workflows/dist/gulpfile.js new file mode 100755 index 0000000..5fc5757 --- /dev/null +++ b/workflows/dist/gulpfile.js @@ -0,0 +1,17 @@ +'use strict'; + +var _gulp = require('gulp'); + +var _gulp2 = _interopRequireDefault(_gulp); + +var _preCheck = require('./utils/pre-check'); + +var _getTasks = require('./utils/get-tasks'); + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + +// Check Node version and workflow setup. +(0, _preCheck.preCheck)(); + +// Define default task. +_gulp2.default.task('default', _gulp2.default.series((0, _getTasks.getTasks)())); \ No newline at end of file diff --git a/workflows/dist/tasks/clean.js b/workflows/dist/tasks/clean.js new file mode 100644 index 0000000..29d75e6 --- /dev/null +++ b/workflows/dist/tasks/clean.js @@ -0,0 +1,31 @@ +'use strict'; + +var _gulp = require('gulp'); + +var _gulp2 = _interopRequireDefault(_gulp); + +var _getConfig = require('../utils/get-config'); + +var _del = require('del'); + +var _del2 = _interopRequireDefault(_del); + +var _TaskHelper = require('../utils/TaskHelper'); + +var _TaskHelper2 = _interopRequireDefault(_TaskHelper); + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + +var task = new _TaskHelper2.default({ + name: 'clean', + requiredPaths: ['src'], + config: _getConfig.tasks +}); + +_gulp2.default.task(task.name, function (done) { + if (task.isValid()) { + (0, _del2.default)(task.src).then(function () { + return done(); + }); + } +}); \ No newline at end of file diff --git a/workflows/dist/tasks/copy.js b/workflows/dist/tasks/copy.js new file mode 100755 index 0000000..c0552df --- /dev/null +++ b/workflows/dist/tasks/copy.js @@ -0,0 +1,35 @@ +'use strict'; + +var _gulp = require('gulp'); + +var _gulp2 = _interopRequireDefault(_gulp); + +var _gulpIf = require('gulp-if'); + +var _gulpIf2 = _interopRequireDefault(_gulpIf); + +var _gulpCached = require('gulp-cached'); + +var _gulpCached2 = _interopRequireDefault(_gulpCached); + +var _getConfig = require('../utils/get-config'); + +var _TaskHelper = require('../utils/TaskHelper'); + +var _TaskHelper2 = _interopRequireDefault(_TaskHelper); + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + +var task = new _TaskHelper2.default({ + name: 'copy', + requiredPaths: ['src', 'dest'], + config: _getConfig.tasks +}); + +_gulp2.default.task(task.name, function () { + if (!task.isValid()) { + return null; + } + + return task.start().pipe((0, _gulpIf2.default)(_getConfig.isDev, (0, _gulpCached2.default)(task.cacheName, { optimizeMemory: false }))).pipe(task.end()); +}); \ No newline at end of file diff --git a/workflows/dist/tasks/css-lint.js b/workflows/dist/tasks/css-lint.js new file mode 100644 index 0000000..25823d7 --- /dev/null +++ b/workflows/dist/tasks/css-lint.js @@ -0,0 +1,54 @@ +'use strict'; + +var _gulp = require('gulp'); + +var _gulp2 = _interopRequireDefault(_gulp); + +var _gulpCached = require('gulp-cached'); + +var _gulpCached2 = _interopRequireDefault(_gulpCached); + +var _getConfig = require('../utils/get-config'); + +var _gulpIf = require('gulp-if'); + +var _gulpIf2 = _interopRequireDefault(_gulpIf); + +var _gulpPostcss = require('gulp-postcss'); + +var _gulpPostcss2 = _interopRequireDefault(_gulpPostcss); + +var _postcssReporter = require('postcss-reporter'); + +var _postcssReporter2 = _interopRequireDefault(_postcssReporter); + +var _postcssScss = require('postcss-scss'); + +var _postcssScss2 = _interopRequireDefault(_postcssScss); + +var _stylelint = require('stylelint'); + +var _stylelint2 = _interopRequireDefault(_stylelint); + +var _TaskHelper = require('../utils/TaskHelper'); + +var _TaskHelper2 = _interopRequireDefault(_TaskHelper); + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + +var task = new _TaskHelper2.default({ + name: 'css-lint', + requiredPaths: ['src'], + config: _getConfig.tasks, + configSlug: 'css' +}); + +if (undefined !== task.config) { + _gulp2.default.task(task.name, function () { + if (!task.isValid()) { + return null; + } + + return task.start().pipe((0, _gulpIf2.default)(_getConfig.isDev, (0, _gulpCached2.default)(task.cacheName))).pipe((0, _gulpPostcss2.default)([(0, _stylelint2.default)(), (0, _postcssReporter2.default)({ clearAllMessages: true })], { syntax: _postcssScss2.default })); + }); +} \ No newline at end of file diff --git a/workflows/dist/tasks/css.js b/workflows/dist/tasks/css.js new file mode 100644 index 0000000..47e84ba --- /dev/null +++ b/workflows/dist/tasks/css.js @@ -0,0 +1,133 @@ +'use strict'; + +var _gulp = require('gulp'); + +var _gulp2 = _interopRequireDefault(_gulp); + +var _gulpCached = require('gulp-cached'); + +var _gulpCached2 = _interopRequireDefault(_gulpCached); + +var _gulpProgeny = require('gulp-progeny'); + +var _gulpProgeny2 = _interopRequireDefault(_gulpProgeny); + +var _getConfig = require('../utils/get-config'); + +var _gulpSass = require('gulp-sass'); + +var _gulpSass2 = _interopRequireDefault(_gulpSass); + +var _gulpSourcemaps = require('gulp-sourcemaps'); + +var _gulpSourcemaps2 = _interopRequireDefault(_gulpSourcemaps); + +var _gulpIf = require('gulp-if'); + +var _gulpIf2 = _interopRequireDefault(_gulpIf); + +var _gulpPostcss = require('gulp-postcss'); + +var _gulpPostcss2 = _interopRequireDefault(_gulpPostcss); + +var _postcssCssnext = require('postcss-cssnext'); + +var _postcssCssnext2 = _interopRequireDefault(_postcssCssnext); + +var _postcssPxtorem = require('postcss-pxtorem'); + +var _postcssPxtorem2 = _interopRequireDefault(_postcssPxtorem); + +var _autoprefixer = require('autoprefixer'); + +var _autoprefixer2 = _interopRequireDefault(_autoprefixer); + +var _postcssAssets = require('postcss-assets'); + +var _postcssAssets2 = _interopRequireDefault(_postcssAssets); + +var _TaskHelper = require('../utils/TaskHelper'); + +var _TaskHelper2 = _interopRequireDefault(_TaskHelper); + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + +var task = new _TaskHelper2.default({ + name: 'css', + requiredPaths: ['src', 'dest'], + config: _getConfig.tasks +}); + +if (undefined !== task.config) { + var fn = function fn() { + if (!task.isValid()) { + return null; + } + + return task.start() + + // Caching and incremental building (progeny) in Gulp. + .pipe((0, _gulpIf2.default)(_getConfig.isDev, (0, _gulpCached2.default)(task.cacheName))).pipe((0, _gulpIf2.default)(_getConfig.isDev, (0, _gulpProgeny2.default)())) + + // Actual SASS compilation. + .pipe((0, _gulpIf2.default)(_getConfig.isDev, _gulpSourcemaps2.default.init())).pipe((0, _gulpSass2.default)({ + includePaths: undefined !== task.config.includePaths ? task.config.includePaths : [], + outputStyle: _getConfig.isDev ? 'expanded' : 'compressed' + }).on('error', _gulpSass2.default.logError)).pipe((0, _gulpPostcss2.default)(getProcessors(task.config.postcssProcessors))).pipe((0, _gulpIf2.default)(_getConfig.isDev, _gulpSourcemaps2.default.write(''))).pipe(task.end()); + }; + + fn.displayName = 'css-compile'; + + if (undefined !== task.config.enableLinter && true === task.config.enableLinter) { + _gulp2.default.task('css', _gulp2.default.series('css-lint', fn)); + } else { + _gulp2.default.task('css', fn); + } +} + +function getProcessors(settings) { + var processors = [], + defaults = void 0, + s = void 0; + + defaults = { + cssnext: { + warnForDuplicates: false + }, + autoprefixer: {}, + pxtorem: { + rootValue: 16, + unitPrecision: 5, + propList: ['*'], + selectorBlackList: [], + replace: true, + mediaQuery: true, + minPixelValue: 2 + }, + assets: { + relative: true + } + }; + + if (false !== settings.cssnext) { + s = true === settings.cssnext ? {} : settings.cssnext; + processors.push((0, _postcssCssnext2.default)(Object.assign(defaults.cssnext, s))); + } + + if (false !== settings.autoprefixer) { + s = true === settings.autoprefixer ? {} : settings.autoprefixer; + processors.push((0, _autoprefixer2.default)(Object.assign(defaults.autoprefixer, s))); + } + + if (false !== settings.pxtorem) { + s = true === settings.pxtorem ? {} : settings.pxtorem; + processors.push((0, _postcssPxtorem2.default)(Object.assign(defaults.pxtorem, s))); + } + + if (false !== settings.assets) { + s = true === settings.assets ? {} : settings.assets; + processors.push((0, _postcssAssets2.default)(Object.assign(defaults.assets, s))); + } + + return processors; +} \ No newline at end of file diff --git a/workflows/dist/tasks/images.js b/workflows/dist/tasks/images.js new file mode 100755 index 0000000..651d809 --- /dev/null +++ b/workflows/dist/tasks/images.js @@ -0,0 +1,39 @@ +'use strict'; + +var _gulp = require('gulp'); + +var _gulp2 = _interopRequireDefault(_gulp); + +var _gulpIf = require('gulp-if'); + +var _gulpIf2 = _interopRequireDefault(_gulpIf); + +var _gulpCached = require('gulp-cached'); + +var _gulpCached2 = _interopRequireDefault(_gulpCached); + +var _gulpImagemin = require('gulp-imagemin'); + +var _gulpImagemin2 = _interopRequireDefault(_gulpImagemin); + +var _getConfig = require('../utils/get-config'); + +var _TaskHelper = require('../utils/TaskHelper'); + +var _TaskHelper2 = _interopRequireDefault(_TaskHelper); + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + +var task = new _TaskHelper2.default({ + name: 'images', + requiredPaths: ['src', 'dest'], + config: _getConfig.tasks +}); + +_gulp2.default.task(task.name, function () { + if (!task.isValid()) { + return null; + } + + return task.start().pipe((0, _gulpIf2.default)(_getConfig.isDev, (0, _gulpCached2.default)(task.cacheName, { optimizeMemory: false }))).pipe((0, _gulpImagemin2.default)()).pipe(task.end()); +}); \ No newline at end of file diff --git a/workflows/dist/tasks/js-lint.js b/workflows/dist/tasks/js-lint.js new file mode 100644 index 0000000..f2b8972 --- /dev/null +++ b/workflows/dist/tasks/js-lint.js @@ -0,0 +1,41 @@ +'use strict'; + +var _gulp = require('gulp'); + +var _gulp2 = _interopRequireDefault(_gulp); + +var _gulpCached = require('gulp-cached'); + +var _gulpCached2 = _interopRequireDefault(_gulpCached); + +var _getConfig = require('../utils/get-config'); + +var _gulpEslint = require('gulp-eslint'); + +var _gulpEslint2 = _interopRequireDefault(_gulpEslint); + +var _gulpIf = require('gulp-if'); + +var _gulpIf2 = _interopRequireDefault(_gulpIf); + +var _TaskHelper = require('../utils/TaskHelper'); + +var _TaskHelper2 = _interopRequireDefault(_TaskHelper); + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + +var task = new _TaskHelper2.default({ + name: 'js-lint', + requiredPaths: ['src'], + config: _getConfig.tasks +}); + +if (undefined !== task.config) { + _gulp2.default.task(task.name, function () { + if (!task.isValid()) { + return null; + } + + return task.start().pipe((0, _gulpIf2.default)(_getConfig.isDev, (0, _gulpCached2.default)(task.cacheName))).pipe((0, _gulpEslint2.default)()).pipe((0, _gulpIf2.default)(_getConfig.isProd, _gulpEslint2.default.format())).pipe((0, _gulpIf2.default)(_getConfig.isProd, _gulpEslint2.default.failAfterError())); + }); +} \ No newline at end of file diff --git a/workflows/dist/tasks/js.js b/workflows/dist/tasks/js.js new file mode 100755 index 0000000..ec4d842 --- /dev/null +++ b/workflows/dist/tasks/js.js @@ -0,0 +1,87 @@ +'use strict'; + +var _getConfig = require('../utils/get-config'); + +var _gulp = require('gulp'); + +var _gulp2 = _interopRequireDefault(_gulp); + +var _path = require('path'); + +var _webpack = require('webpack'); + +var _webpack2 = _interopRequireDefault(_webpack); + +var _webpackStream = require('webpack-stream'); + +var _webpackStream2 = _interopRequireDefault(_webpackStream); + +var _progressBarWebpackPlugin = require('progress-bar-webpack-plugin'); + +var _progressBarWebpackPlugin2 = _interopRequireDefault(_progressBarWebpackPlugin); + +var _webpackConfigUtils = require('webpack-config-utils'); + +var _gulpPlumber = require('gulp-plumber'); + +var _gulpPlumber2 = _interopRequireDefault(_gulpPlumber); + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + +if (undefined !== _getConfig.tasks.js) { + var fn = function fn() { + var babelifyOptions = void 0, + webpackConfig = void 0, + esLintOptions = {}; + var paths = _getConfig.tasks.js; + + babelifyOptions = { + presets: [['env', { + targets: { + browsers: _getConfig.browserslist + } + }]] + }; + + // Avoid linting for the test environment + if (_getConfig.isDev || _getConfig.isProd) { + esLintOptions = { + test: /\.js$/, + loader: 'eslint-loader', + exclude: /(node_modules)/ + }; + } + + webpackConfig = { + context: (0, _path.resolve)(_getConfig.cwd, paths.base), + entry: paths.entry, + output: { + filename: '[name].js', + pathinfo: _getConfig.isDev + }, + devtool: _getConfig.isProd ? 'source-map' : 'eval', + module: { + rules: [esLintOptions], + loaders: [{ + test: /\.js$/, + loader: 'babel-loader', + options: babelifyOptions, + exclude: /node_modules/ + }] + }, + plugins: (0, _webpackConfigUtils.removeEmpty)([new _progressBarWebpackPlugin2.default(), _getConfig.isProd ? new _webpack2.default.optimize.UglifyJsPlugin() : undefined]), + watch: true, + cache: true + }; + + return _gulp2.default.src((0, _path.resolve)(_getConfig.cwd, paths.base)).pipe((0, _gulpPlumber2.default)()).pipe((0, _webpackStream2.default)(webpackConfig, _webpack2.default)).pipe(_gulpPlumber2.default.stop()).pipe(_gulp2.default.dest((0, _path.resolve)(_getConfig.cwd, paths.dest))); + }; + + fn.displayName = 'js-compile'; + + if (undefined !== _getConfig.tasks['js-lint']) { + _gulp2.default.task('js', _gulp2.default.series('js-lint', fn)); + } else { + _gulp2.default.task('js', fn); + } +} \ No newline at end of file diff --git a/workflows/dist/tasks/watch.js b/workflows/dist/tasks/watch.js new file mode 100644 index 0000000..0c25a20 --- /dev/null +++ b/workflows/dist/tasks/watch.js @@ -0,0 +1,39 @@ +'use strict'; + +var _gulp = require('gulp'); + +var _gulp2 = _interopRequireDefault(_gulp); + +var _getConfig = require('../utils/get-config'); + +var _gulpWatch = require('gulp-watch'); + +var _gulpWatch2 = _interopRequireDefault(_gulpWatch); + +var _path = require('path'); + +var _without2 = require('lodash/without'); + +var _without3 = _interopRequireDefault(_without2); + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + +if (undefined !== _getConfig.tasks.watch && undefined !== _getConfig.tasks.watch.tasks) { + _gulp2.default.task('watch', function () { + + // Omit some tasks, e.g. `js` is already watched by Webpack. + var filteredTasks = (0, _without3.default)(_getConfig.tasks.watch.tasks, 'js', 'js-lint', 'clean'); + + filteredTasks.forEach(function (taskSlug) { + var task = _getConfig.tasks[taskSlug]; + + if (undefined === task.src) { + return; + } + + (0, _gulpWatch2.default)((0, _path.join)(_getConfig.cwd, task.src), function () { + return _gulp2.default.start(taskSlug); + }); + }); + }); +} \ No newline at end of file diff --git a/workflows/dist/utils/TaskHelper.js b/workflows/dist/utils/TaskHelper.js new file mode 100644 index 0000000..0dda51e --- /dev/null +++ b/workflows/dist/utils/TaskHelper.js @@ -0,0 +1,137 @@ +'use strict'; + +Object.defineProperty(exports, "__esModule", { + value: true +}); + +var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); + +var _gulp = require('gulp'); + +var _gulp2 = _interopRequireDefault(_gulp); + +var _gulpUtil = require('gulp-util'); + +var _gulpUtil2 = _interopRequireDefault(_gulpUtil); + +var _path = require('path'); + +var _getConfig = require('./get-config'); + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + +function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } + +var TaskHelper = function () { + function TaskHelper(_ref) { + var _ref$name = _ref.name, + name = _ref$name === undefined ? '' : _ref$name, + _ref$requiredPaths = _ref.requiredPaths, + requiredPaths = _ref$requiredPaths === undefined ? [] : _ref$requiredPaths, + _ref$config = _ref.config, + config = _ref$config === undefined ? null : _ref$config, + _ref$configSlug = _ref.configSlug, + configSlug = _ref$configSlug === undefined ? '' : _ref$configSlug; + + _classCallCheck(this, TaskHelper); + + if (null === config) { + _gulpUtil2.default.log(_gulpUtil2.default.colors.red('The task template is missing a configuration.')); + return; + } + + this._name = name; + this._requiredPaths = requiredPaths; + this._config = config; + this._configSlug = '' === configSlug ? name : configSlug; + } + + _createClass(TaskHelper, [{ + key: 'isValid', + value: function isValid() { + if (!this.hasPathsDefined) { + _gulpUtil2.default.log('Missing paths in \'' + _gulpUtil2.default.colors.red(this.name) + '\' task, aborting!'); + return false; + } + return true; + } + }, { + key: 'start', + value: function start() { + return _gulp2.default.src(this.src, { base: this.base }); + } + }, { + key: 'end', + value: function end() { + return _gulp2.default.dest(this.dest, { cwd: _getConfig.cwd }); + } + }, { + key: 'config', + get: function get() { + return '' === this.configSlug ? this._config : this._config[this.configSlug]; + } + }, { + key: 'name', + get: function get() { + return this._name; + } + }, { + key: 'configSlug', + get: function get() { + return this._configSlug; + } + }, { + key: 'requiredPaths', + get: function get() { + return this._requiredPaths; + } + }, { + key: 'hasPathsDefined', + get: function get() { + var _this = this; + + return this.requiredPaths.every(function (path) { + return undefined !== _this.config[path]; + }); + } + }, { + key: 'src', + get: function get() { + var srcList = Array.isArray(this.config.src) ? this.config.src : [this.config.src], + src = srcList.map(function (path) { + return (0, _path.join)(_getConfig.cwd, path); + }); + + return src; + } + }, { + key: 'entries', + get: function get() { + var entriesList = Array.isArray(this.config.entries) ? this.config.entries : [this.config.entries], + entries = entriesList.map(function (path) { + return (0, _path.join)(_getConfig.cwd, path); + }); + + return entries; + } + }, { + key: 'base', + get: function get() { + return undefined === this.config.base ? '' : (0, _path.join)(_getConfig.cwd, this.config.base); + } + }, { + key: 'dest', + get: function get() { + return this.config.dest; + } + }, { + key: 'cacheName', + get: function get() { + return this.name + '-task-cache'; + } + }]); + + return TaskHelper; +}(); + +exports.default = TaskHelper; \ No newline at end of file diff --git a/workflows/dist/utils/get-config.js b/workflows/dist/utils/get-config.js new file mode 100644 index 0000000..bf18115 --- /dev/null +++ b/workflows/dist/utils/get-config.js @@ -0,0 +1,84 @@ +'use strict'; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.browserslist = exports.workflow = exports.isProd = exports.isTest = exports.isDev = exports.cwd = exports.env = exports.tasks = exports.json = undefined; + +var _fs = require('fs'); + +var _fs2 = _interopRequireDefault(_fs); + +var _yargs = require('yargs'); + +var _yargs2 = _interopRequireDefault(_yargs); + +var _path = require('path'); + +var _gulpUtil = require('gulp-util'); + +var _gulpUtil2 = _interopRequireDefault(_gulpUtil); + +var _defaultsDeep2 = require('lodash/defaultsDeep'); + +var _defaultsDeep3 = _interopRequireDefault(_defaultsDeep2); + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + +var json = JSON.parse(_fs2.default.readFileSync('./package.json')), + env = _yargs2.default.argv.env, + workflow = _yargs2.default.argv.workflow, + browserslist = json.browserslist; + +var tasks = [], + cwd = '', + schema = '', + isTest = false, + isProd = false, + isDev = false; + +switch (env) { + case 'test': + exports.isTest = isTest = true; + break; + case 'prod': + case 'production': + exports.isProd = isProd = true; + break; + default: + exports.isDev = isDev = true; +} + +if (undefined !== workflow && undefined !== json.workflows[workflow]) { + exports.tasks = tasks = json.workflows[workflow]; +} +if (undefined !== tasks.cwd) { + exports.cwd = cwd = tasks.cwd; + delete tasks.cwd; +} + +function getSchema(slug) { + var file = (0, _path.resolve)(__dirname, '../../schemas/' + slug + '.json'); + + if (!_fs2.default.existsSync(file)) { + _gulpUtil2.default.log(_gulpUtil2.default.colors.yellow('Schema \'' + slug + '\' not found, ignoring...')); + return {}; + } + + return JSON.parse(_fs2.default.readFileSync(file)); +} +if (undefined !== tasks.schema) { + schema = getSchema(tasks.schema); + delete tasks.schema; + exports.tasks = tasks = (0, _defaultsDeep3.default)(tasks, schema); +} + +exports.json = json; +exports.tasks = tasks; +exports.env = env; +exports.cwd = cwd; +exports.isDev = isDev; +exports.isTest = isTest; +exports.isProd = isProd; +exports.workflow = workflow; +exports.browserslist = browserslist; \ No newline at end of file diff --git a/workflows/dist/utils/get-tasks.js b/workflows/dist/utils/get-tasks.js new file mode 100644 index 0000000..208fb24 --- /dev/null +++ b/workflows/dist/utils/get-tasks.js @@ -0,0 +1,58 @@ +'use strict'; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.getTasks = undefined; + +var _gulp = require('gulp'); + +var _gulp2 = _interopRequireDefault(_gulp); + +var _gulpUtil = require('gulp-util'); + +var _gulpUtil2 = _interopRequireDefault(_gulpUtil); + +var _requireDir = require('require-dir'); + +var _requireDir2 = _interopRequireDefault(_requireDir); + +var _getConfig = require('./get-config'); + +var _sortTasks = require('./sort-tasks'); + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + +var getTasks = exports.getTasks = function getTasks() { + var tasksList = void 0, + gulpTasks = void 0; + + // Load all Gulp tasks from `tasks` dir. + (0, _requireDir2.default)('../tasks'); + + // Filter the list to only contain existing Gulp tasks. + tasksList = Object.keys(_getConfig.tasks).filter(function (task) { + if (undefined === _gulp2.default.task(task)) { + _gulpUtil2.default.log('Task \'' + _gulpUtil2.default.colors.red(task) + '\' is not defined, ignoring!'); + return false; + } + + return true; + }); + + // Sort tasks into `before`, `after` and `tasks` lists. + tasksList = (0, _sortTasks.sortTasks)(tasksList, ['js-lint']); + gulpTasks = []; + + if (0 < tasksList.before.length) { + gulpTasks.push(_gulp2.default.parallel(tasksList.before)); + } + if (0 < tasksList.tasks.length) { + gulpTasks.push(_gulp2.default.parallel(tasksList.tasks)); + } + if (0 < tasksList.after.length) { + gulpTasks.push(_gulp2.default.parallel(tasksList.after)); + } + + return gulpTasks; +}; \ No newline at end of file diff --git a/workflows/dist/utils/pre-check.js b/workflows/dist/utils/pre-check.js new file mode 100644 index 0000000..fc953e6 --- /dev/null +++ b/workflows/dist/utils/pre-check.js @@ -0,0 +1,39 @@ +'use strict'; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.preCheck = undefined; + +var _gulpUtil = require('gulp-util'); + +var _gulpUtil2 = _interopRequireDefault(_gulpUtil); + +var _validateNodeVersion = require('validate-node-version'); + +var _validateNodeVersion2 = _interopRequireDefault(_validateNodeVersion); + +var _getConfig = require('./get-config'); + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + +var preCheck = exports.preCheck = function preCheck() { + var nodeTest = (0, _validateNodeVersion2.default)(), + exitCode = 1; + + if (!nodeTest.satisfies) { + _gulpUtil2.default.log(_gulpUtil2.default.colors.red(nodeTest.message)); + process.exit(exitCode); + } + + if (undefined === _getConfig.workflow) { + _gulpUtil2.default.log(_gulpUtil2.default.colors.red('No workflow provided, aborting!')); + process.exit(exitCode); + } else { + _gulpUtil2.default.log('Using \'' + _gulpUtil2.default.colors.yellow(_getConfig.workflow) + '\' workflow...'); + } + + if (undefined !== _getConfig.env) { + _gulpUtil2.default.log('Using \'' + _gulpUtil2.default.colors.yellow(_getConfig.env) + '\' environment...'); + } +}; \ No newline at end of file diff --git a/workflows/dist/utils/sort-tasks.js b/workflows/dist/utils/sort-tasks.js new file mode 100644 index 0000000..e380a51 --- /dev/null +++ b/workflows/dist/utils/sort-tasks.js @@ -0,0 +1,39 @@ +'use strict'; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +/** + * Split tasks into 3 categories: main tasks, before and after. + * + * @param {Array} allTasks Set of all tasks + * @param {Array} ignoredTasks Tasks to ignore + * @return {{before: Array, tasks: Array, after: Array}} Categorized tasks object + */ +var sortTasks = exports.sortTasks = function sortTasks(allTasks, ignoredTasks) { + var tasks = allTasks, + before = [], + after = []; + + if (undefined !== ignoredTasks) { + tasks = tasks.filter(function (task) { + return !ignoredTasks.includes(task); + }); + } + + if (tasks.includes('clean')) { + before.push('clean'); + tasks = tasks.filter(function (task) { + return 'clean' !== task; + }); + } + + if (tasks.includes('watch')) { + after.push('watch'); + tasks = tasks.filter(function (task) { + return 'watch' !== task; + }); + } + + return { before: before, tasks: tasks, after: after }; +}; \ No newline at end of file diff --git a/workflows/dist/utils/sort-tasks.test.js b/workflows/dist/utils/sort-tasks.test.js new file mode 100755 index 0000000..2c64754 --- /dev/null +++ b/workflows/dist/utils/sort-tasks.test.js @@ -0,0 +1,77 @@ +'use strict'; + +var _sortTasks = require('./sort-tasks'); + +describe('sortTasks()', function () { + test('returns before and main tasks', function () { + expect((0, _sortTasks.sortTasks)(['clean', 'js', 'css'])).toEqual({ + before: ['clean'], + tasks: ['js', 'css'], + after: [] + }); + }); + + test('returns before, after and main tasks', function () { + expect((0, _sortTasks.sortTasks)(['watch', 'clean', 'js', 'css'])).toEqual({ + before: ['clean'], + tasks: ['js', 'css'], + after: ['watch'] + }); + }); + + test('returns after and main tasks', function () { + expect((0, _sortTasks.sortTasks)(['watch', 'js', 'css'])).toEqual({ + before: [], + tasks: ['js', 'css'], + after: ['watch'] + }); + }); + + test('returns after tasks only', function () { + expect((0, _sortTasks.sortTasks)(['watch'])).toEqual({ + before: [], + tasks: [], + after: ['watch'] + }); + }); + + test('returns main tasks only', function () { + expect((0, _sortTasks.sortTasks)(['css'])).toEqual({ + before: [], + tasks: ['css'], + after: [] + }); + }); + + test('returns before tasks only', function () { + expect((0, _sortTasks.sortTasks)(['clean'])).toEqual({ + before: ['clean'], + tasks: [], + after: [] + }); + }); + + test('specified tasks are ignored', function () { + expect((0, _sortTasks.sortTasks)(['watch', 'clean', 'js', 'css'], ['css'])).toEqual({ + before: ['clean'], + tasks: ['js'], + after: ['watch'] + }); + }); + + test('specified tasks are ignored', function () { + expect((0, _sortTasks.sortTasks)(['watch', 'clean', 'js', 'css'], ['clean'])).toEqual({ + before: [], + tasks: ['js', 'css'], + after: ['watch'] + }); + }); + + test('specified tasks are ignored', function () { + expect((0, _sortTasks.sortTasks)(['watch', 'clean', 'js', 'css'], ['watch'])).toEqual({ + before: ['clean'], + tasks: ['js', 'css'], + after: [] + }); + }); +}); /* eslint-env jest */ \ No newline at end of file diff --git a/workflows/schemas/theme.json b/workflows/schemas/theme.json new file mode 100644 index 0000000..32f18e1 --- /dev/null +++ b/workflows/schemas/theme.json @@ -0,0 +1,46 @@ +{ + "clean": { + "src": "dist" + }, + "css": { + "src": "src/css/**/*.scss", + "base": "src/css", + "dest": "dist/css", + "includePaths": [ + "node_modules" + ], + "enableLinter": true, + "postcssProcessors": { + "cssnext": true, + "autoprefixer": { + "minPixelValue": 0 + }, + "pxtorem": true, + "assets": true + } + }, + "js": { + "base": "src/js", + "dest": "dist/js", + "entry": { + "main": "./main.js" + } + }, + "images": { + "src": "images/**/*.+(png|jpg|jpeg|gif|bmp|svg)", + "base": "images", + "dest": "images" + }, + "copy": { + "src": "src/fonts/**/*", + "base": "src/fonts", + "dest": "dist/fonts" + }, + "watch": { + "tasks": [ + "css", + "copy", + "images" + ] + } +} \ No newline at end of file diff --git a/workflows/src/gulpfile.babel.js b/workflows/src/gulpfile.babel.js new file mode 100755 index 0000000..298fc3f --- /dev/null +++ b/workflows/src/gulpfile.babel.js @@ -0,0 +1,9 @@ +import gulp from 'gulp'; +import { preCheck } from './utils/pre-check'; +import { getTasks } from './utils/get-tasks'; + +// Check Node version and workflow setup. +preCheck(); + +// Define default task. +gulp.task( 'default', gulp.series( getTasks() ) ); diff --git a/workflows/src/tasks/clean.js b/workflows/src/tasks/clean.js new file mode 100644 index 0000000..0295a13 --- /dev/null +++ b/workflows/src/tasks/clean.js @@ -0,0 +1,16 @@ +import gulp from 'gulp'; +import { tasks } from '../utils/get-config'; +import del from 'del'; +import TaskHelper from '../utils/TaskHelper'; + +const task = new TaskHelper( { + name: 'clean', + requiredPaths: ['src'], + config: tasks +} ); + +gulp.task( task.name, done => { + if ( task.isValid() ) { + del( task.src ).then( () => done() ); + } +} ); diff --git a/workflows/src/tasks/copy.js b/workflows/src/tasks/copy.js new file mode 100755 index 0000000..04a7cbc --- /dev/null +++ b/workflows/src/tasks/copy.js @@ -0,0 +1,21 @@ +import gulp from 'gulp'; +import gulpIf from 'gulp-if'; +import cache from 'gulp-cached'; +import { tasks, isDev } from '../utils/get-config'; +import TaskHelper from '../utils/TaskHelper'; + +const task = new TaskHelper( { + name: 'copy', + requiredPaths: [ 'src', 'dest' ], + config: tasks +} ); + +gulp.task( task.name, () => { + if ( ! task.isValid() ) { + return null; + } + + return task.start() + .pipe( gulpIf( isDev, cache( task.cacheName, { optimizeMemory: false } ) ) ) + .pipe( task.end() ); +} ); diff --git a/workflows/src/tasks/css-lint.js b/workflows/src/tasks/css-lint.js new file mode 100644 index 0000000..90bc0e2 --- /dev/null +++ b/workflows/src/tasks/css-lint.js @@ -0,0 +1,31 @@ +import gulp from 'gulp'; +import cache from 'gulp-cached'; +import { tasks, isDev } from '../utils/get-config'; +import gulpIf from 'gulp-if'; +import postcss from 'gulp-postcss'; +import reporter from 'postcss-reporter'; +import scss from 'postcss-scss'; +import stylelint from 'stylelint'; +import TaskHelper from '../utils/TaskHelper'; + +const task = new TaskHelper( { + name: 'css-lint', + requiredPaths: ['src'], + config: tasks, + configSlug: 'css' +} ); + +if ( undefined !== task.config ) { + gulp.task( task.name, () => { + if ( ! task.isValid() ) { + return null; + } + + return task.start() + .pipe( gulpIf( isDev, cache( task.cacheName ) ) ) + .pipe( postcss( [ + stylelint(), + reporter( { clearAllMessages: true } ) + ], { syntax: scss } ) ); + } ); +} diff --git a/workflows/src/tasks/css.js b/workflows/src/tasks/css.js new file mode 100644 index 0000000..9bf4bc4 --- /dev/null +++ b/workflows/src/tasks/css.js @@ -0,0 +1,97 @@ +import gulp from 'gulp'; +import cache from 'gulp-cached'; +import progeny from 'gulp-progeny'; +import { tasks, isDev } from '../utils/get-config'; +import sass from 'gulp-sass'; +import sourcemaps from 'gulp-sourcemaps'; +import gulpIf from 'gulp-if'; +import postcss from 'gulp-postcss'; +import cssnext from 'postcss-cssnext'; +import pxtorem from 'postcss-pxtorem'; +import autoprefixer from 'autoprefixer'; +import assets from 'postcss-assets'; +import TaskHelper from '../utils/TaskHelper'; + +const task = new TaskHelper( { + name: 'css', + requiredPaths: [ 'src', 'dest' ], + config: tasks +} ); + +if ( undefined !== task.config ) { + let fn = function() { + if ( ! task.isValid() ) { + return null; + } + + return task.start() + + // Caching and incremental building (progeny) in Gulp. + .pipe( gulpIf( isDev, cache( task.cacheName ) ) ) + .pipe( gulpIf( isDev, progeny() ) ) + + // Actual SASS compilation. + .pipe( gulpIf( isDev, sourcemaps.init() ) ) + .pipe( sass( { + includePaths: undefined !== task.config.includePaths ? task.config.includePaths : [], + outputStyle: isDev ? 'expanded' : 'compressed' + } ).on( 'error', sass.logError ) ) + .pipe( postcss( getProcessors( task.config.postcssProcessors ) ) ) + .pipe( gulpIf( isDev, sourcemaps.write( '' ) ) ) + + .pipe( task.end() ); + }; + + fn.displayName = 'css-compile'; + + if ( undefined !== task.config.enableLinter && true === task.config.enableLinter ) { + gulp.task( 'css', gulp.series( 'css-lint', fn ) ); + } else { + gulp.task( 'css', fn ); + } +} + +function getProcessors( settings ) { + let processors = [], defaults, s; + + defaults = { + cssnext: { + warnForDuplicates: false + }, + autoprefixer: {}, + pxtorem: { + rootValue: 16, + unitPrecision: 5, + propList: [ '*' ], + selectorBlackList: [], + replace: true, + mediaQuery: true, + minPixelValue: 2 + }, + assets: { + relative: true + } + }; + + if ( false !== settings.cssnext ) { + s = true === settings.cssnext ? {} : settings.cssnext; + processors.push( cssnext( Object.assign( defaults.cssnext, s ) ) ); + } + + if ( false !== settings.autoprefixer ) { + s = true === settings.autoprefixer ? {} : settings.autoprefixer; + processors.push( autoprefixer( Object.assign( defaults.autoprefixer, s ) ) ); + } + + if ( false !== settings.pxtorem ) { + s = true === settings.pxtorem ? {} : settings.pxtorem; + processors.push( pxtorem( Object.assign( defaults.pxtorem, s ) ) ); + } + + if ( false !== settings.assets ) { + s = true === settings.assets ? {} : settings.assets; + processors.push( assets( Object.assign( defaults.assets, s ) ) ); + } + + return processors; +} diff --git a/workflows/src/tasks/images.js b/workflows/src/tasks/images.js new file mode 100755 index 0000000..d06bd01 --- /dev/null +++ b/workflows/src/tasks/images.js @@ -0,0 +1,23 @@ +import gulp from 'gulp'; +import gulpIf from 'gulp-if'; +import cache from 'gulp-cached'; +import imagemin from 'gulp-imagemin'; +import { tasks, isDev } from '../utils/get-config'; +import TaskHelper from '../utils/TaskHelper'; + +const task = new TaskHelper( { + name: 'images', + requiredPaths: [ 'src', 'dest' ], + config: tasks +} ); + +gulp.task( task.name, () => { + if ( ! task.isValid() ) { + return null; + } + + return task.start() + .pipe( gulpIf( isDev, cache( task.cacheName, { optimizeMemory: false } ) ) ) + .pipe( imagemin() ) + .pipe( task.end() ); +} ); diff --git a/workflows/src/tasks/js-lint.js b/workflows/src/tasks/js-lint.js new file mode 100644 index 0000000..30763d2 --- /dev/null +++ b/workflows/src/tasks/js-lint.js @@ -0,0 +1,27 @@ +import gulp from 'gulp'; +import cache from 'gulp-cached'; +import { tasks, isDev, isProd } from '../utils/get-config'; +import eslint from 'gulp-eslint'; +import gulpIf from 'gulp-if'; +import TaskHelper from '../utils/TaskHelper'; + +const task = new TaskHelper( { + name: 'js-lint', + requiredPaths: ['src'], + config: tasks +} ); + +if ( undefined !== task.config ) { + gulp.task( task.name, () => { + if ( ! task.isValid() ) { + return null; + } + + return task.start() + .pipe( gulpIf( isDev, cache( task.cacheName ) ) ) + .pipe( eslint() ) + .pipe( gulpIf( isProd, eslint.format() ) ) + .pipe( gulpIf( isProd, eslint.failAfterError() ) ); + } ); +} + diff --git a/workflows/src/tasks/js.js b/workflows/src/tasks/js.js new file mode 100755 index 0000000..e6358f0 --- /dev/null +++ b/workflows/src/tasks/js.js @@ -0,0 +1,77 @@ +import { tasks, isProd, isDev, browserslist, cwd } from '../utils/get-config'; +import gulp from 'gulp'; +import { resolve } from 'path'; +import webpack from 'webpack'; +import webpackStream from 'webpack-stream'; +import ProgressBarPlugin from 'progress-bar-webpack-plugin'; +import { removeEmpty } from 'webpack-config-utils'; +import plumber from 'gulp-plumber'; + +if ( undefined !== tasks.js ) { + let fn = function() { + let babelifyOptions, webpackConfig, esLintOptions = {}; + const paths = tasks.js; + + babelifyOptions = { + presets: [ + [ 'env', { + targets: { + browsers: browserslist + } + } ] + ] + }; + + // Avoid linting for the test environment + if ( isDev || isProd ) { + esLintOptions = { + test: /\.js$/, + loader: 'eslint-loader', + exclude: /(node_modules)/ + }; + } + + webpackConfig = { + context: resolve( cwd, paths.base ), + entry: paths.entry, + output: { + filename: '[name].js', + pathinfo: isDev, + }, + devtool: isProd ? 'source-map': 'eval', + module: { + rules: [ + esLintOptions + ], + loaders: [ + { + test: /\.js$/, + loader: 'babel-loader', + options: babelifyOptions, + exclude: /node_modules/ + }, + ], + }, + plugins: removeEmpty([ + new ProgressBarPlugin(), + isProd ? new webpack.optimize.UglifyJsPlugin() : undefined + ]), + watch: true, + cache: true, + }; + + return gulp.src( resolve( cwd, paths.base ) ) + .pipe( plumber() ) + .pipe( webpackStream( webpackConfig, webpack ) ) + .pipe( plumber.stop() ) + .pipe( gulp.dest( resolve( cwd, paths.dest ) ) ); + }; + + fn.displayName = 'js-compile'; + + if ( undefined !== tasks['js-lint'] ) { + gulp.task( 'js', gulp.series( 'js-lint', fn ) ); + } else { + gulp.task( 'js', fn ); + } +} diff --git a/workflows/src/tasks/watch.js b/workflows/src/tasks/watch.js new file mode 100644 index 0000000..a0da0dc --- /dev/null +++ b/workflows/src/tasks/watch.js @@ -0,0 +1,23 @@ +import gulp from 'gulp'; +import { tasks, cwd } from '../utils/get-config'; +import watch from 'gulp-watch'; +import { join } from 'path'; +import _without from 'lodash/without'; + +if ( undefined !== tasks.watch && undefined !== tasks.watch.tasks ) { + gulp.task( 'watch', () => { + + // Omit some tasks, e.g. `js` is already watched by Webpack. + const filteredTasks = _without( tasks.watch.tasks, 'js', 'js-lint', 'clean' ); + + filteredTasks.forEach( taskSlug => { + const task = tasks[ taskSlug ]; + + if ( undefined === task.src ) { + return; + } + + watch( join( cwd, task.src ), () => gulp.start( taskSlug ) ); + } ); + } ); +} diff --git a/workflows/src/utils/TaskHelper.js b/workflows/src/utils/TaskHelper.js new file mode 100644 index 0000000..b3fbc90 --- /dev/null +++ b/workflows/src/utils/TaskHelper.js @@ -0,0 +1,80 @@ +import gulp from 'gulp'; +import gutil from 'gulp-util'; +import { join } from 'path'; +import { cwd } from './get-config'; + +export default class TaskHelper { + constructor( { name = '', requiredPaths = [], config = null, configSlug = '' } ) { + if ( null === config ) { + gutil.log( gutil.colors.red( 'The task template is missing a configuration.' ) ); + return; + } + + this._name = name; + this._requiredPaths = requiredPaths; + this._config = config; + this._configSlug = '' === configSlug ? name : configSlug; + } + + get config() { + return '' === this.configSlug ? this._config : this._config[ this.configSlug ]; + } + + get name() { + return this._name; + } + + get configSlug() { + return this._configSlug; + } + + get requiredPaths() { + return this._requiredPaths; + } + + get hasPathsDefined() { + return this.requiredPaths.every( path => undefined !== this.config[ path ] ); + } + + get src() { + const srcList = Array.isArray( this.config.src ) ? this.config.src : [ this.config.src ], + src = srcList.map( path => join( cwd, path ) ); + + return src; + } + + get entries() { + const entriesList = Array.isArray( this.config.entries ) ? this.config.entries : [ this.config.entries ], + entries = entriesList.map( path => join( cwd, path ) ); + + return entries; + } + + get base() { + return undefined === this.config.base ? '' : join( cwd, this.config.base ); + } + + get dest() { + return this.config.dest; + } + + get cacheName() { + return `${ this.name }-task-cache`; + } + + isValid() { + if ( ! this.hasPathsDefined ) { + gutil.log( `Missing paths in '${ gutil.colors.red( this.name ) }' task, aborting!` ); + return false; + } + return true; + } + + start() { + return gulp.src( this.src, { base: this.base } ); + } + + end() { + return gulp.dest( this.dest, { cwd } ); + } +} diff --git a/workflows/src/utils/get-config.js b/workflows/src/utils/get-config.js new file mode 100644 index 0000000..ace7f55 --- /dev/null +++ b/workflows/src/utils/get-config.js @@ -0,0 +1,55 @@ +import fs from 'fs'; +import yargs from 'yargs'; +import { resolve } from 'path'; +import gutil from 'gulp-util'; +import _defaultsDeep from 'lodash/defaultsDeep'; + +const json = JSON.parse( fs.readFileSync( './package.json' ) ), + env = yargs.argv.env, + workflow = yargs.argv.workflow, + browserslist = json.browserslist; + +let tasks = [], + cwd = '', + schema = '', + isTest = false, + isProd = false, + isDev = false; + +switch ( env ) { +case 'test': + isTest = true; + break; +case 'prod': +case 'production': + isProd = true; + break; +default: + isDev = true; +} + +if ( undefined !== workflow && undefined !== json.workflows[ workflow ] ) { + tasks = json.workflows[ workflow ]; +} +if ( undefined !== tasks.cwd ) { + cwd = tasks.cwd; + delete tasks.cwd; +} + +function getSchema( slug ) { + const file = resolve( __dirname, `../../schemas/${ slug }.json` ); + + if ( ! fs.existsSync( file ) ) { + gutil.log( gutil.colors.yellow( `Schema '${ slug }' not found, ignoring...` ) ); + return {}; + } + + return JSON.parse( fs.readFileSync( file ) ); +} +if ( undefined !== tasks.schema ) { + schema = getSchema( tasks.schema ); + delete tasks.schema; + tasks = _defaultsDeep( tasks, schema ); +} + +export { json, tasks, env, cwd, isDev, isTest, isProd, workflow, browserslist }; diff --git a/workflows/src/utils/get-tasks.js b/workflows/src/utils/get-tasks.js new file mode 100644 index 0000000..80b7f41 --- /dev/null +++ b/workflows/src/utils/get-tasks.js @@ -0,0 +1,38 @@ +import gulp from 'gulp'; +import gutil from 'gulp-util'; +import requireDir from 'require-dir'; +import { tasks } from './get-config'; +import { sortTasks } from './sort-tasks'; + +export const getTasks = function() { + let tasksList, gulpTasks; + + // Load all Gulp tasks from `tasks` dir. + requireDir( '../tasks' ); + + // Filter the list to only contain existing Gulp tasks. + tasksList = Object.keys( tasks ).filter( task => { + if ( undefined === gulp.task( task ) ) { + gutil.log( `Task '${ gutil.colors.red( task ) }' is not defined, ignoring!` ); + return false; + } + + return true; + } ); + + // Sort tasks into `before`, `after` and `tasks` lists. + tasksList = sortTasks( tasksList, [ 'js-lint' ] ); + gulpTasks = []; + + if ( 0 < tasksList.before.length ) { + gulpTasks.push( gulp.parallel( tasksList.before ) ); + } + if ( 0 < tasksList.tasks.length ) { + gulpTasks.push( gulp.parallel( tasksList.tasks ) ); + } + if ( 0 < tasksList.after.length ) { + gulpTasks.push( gulp.parallel( tasksList.after ) ); + } + + return gulpTasks; +}; diff --git a/workflows/src/utils/pre-check.js b/workflows/src/utils/pre-check.js new file mode 100644 index 0000000..13790c6 --- /dev/null +++ b/workflows/src/utils/pre-check.js @@ -0,0 +1,24 @@ +import gutil from 'gulp-util'; +import validateNode from 'validate-node-version'; +import { workflow, env } from './get-config'; + +export const preCheck = function() { + const nodeTest = validateNode(), + exitCode = 1; + + if ( ! nodeTest.satisfies ) { + gutil.log( gutil.colors.red( nodeTest.message ) ); + process.exit( exitCode ); + } + + if ( undefined === workflow ) { + gutil.log( gutil.colors.red( `No workflow provided, aborting!` ) ); + process.exit( exitCode ); + } else { + gutil.log( `Using '${ gutil.colors.yellow( workflow ) }' workflow...` ); + } + + if ( undefined !== env ) { + gutil.log( `Using '${ gutil.colors.yellow( env ) }' environment...` ); + } +}; diff --git a/workflows/src/utils/sort-tasks.js b/workflows/src/utils/sort-tasks.js new file mode 100644 index 0000000..a743356 --- /dev/null +++ b/workflows/src/utils/sort-tasks.js @@ -0,0 +1,26 @@ +/** + * Split tasks into 3 categories: main tasks, before and after. + * + * @param {Array} allTasks Set of all tasks + * @param {Array} ignoredTasks Tasks to ignore + * @return {{before: Array, tasks: Array, after: Array}} Categorized tasks object + */ +export const sortTasks = function( allTasks, ignoredTasks ) { + let tasks = allTasks, before = [], after = []; + + if ( undefined !== ignoredTasks ) { + tasks = tasks.filter( task => ! ignoredTasks.includes( task ) ); + } + + if ( tasks.includes( 'clean' ) ) { + before.push( 'clean' ); + tasks = tasks.filter( task => 'clean' !== task ); + } + + if ( tasks.includes( 'watch' ) ) { + after.push( 'watch' ); + tasks = tasks.filter( task => 'watch' !== task ); + } + + return { before, tasks, after }; +}; diff --git a/workflows/src/utils/sort-tasks.test.js b/workflows/src/utils/sort-tasks.test.js new file mode 100755 index 0000000..26023aa --- /dev/null +++ b/workflows/src/utils/sort-tasks.test.js @@ -0,0 +1,77 @@ +/* eslint-env jest */ + +import { sortTasks } from './sort-tasks'; + +describe( 'sortTasks()', () => { + test( 'returns before and main tasks', () => { + expect( sortTasks( [ 'clean', 'js', 'css' ] ) ).toEqual( { + before: [ 'clean' ], + tasks: [ 'js', 'css' ], + after: [] + } ); + } ); + + test( 'returns before, after and main tasks', () => { + expect( sortTasks( [ 'watch', 'clean', 'js', 'css' ] ) ).toEqual( { + before: [ 'clean' ], + tasks: [ 'js', 'css' ], + after: [ 'watch' ] + } ); + } ); + + test( 'returns after and main tasks', () => { + expect( sortTasks( [ 'watch', 'js', 'css' ] ) ).toEqual( { + before: [], + tasks: [ 'js', 'css' ], + after: [ 'watch' ] + } ); + } ); + + test( 'returns after tasks only', () => { + expect( sortTasks( [ 'watch' ] ) ).toEqual( { + before: [], + tasks: [], + after: [ 'watch' ] + } ); + } ); + + test( 'returns main tasks only', () => { + expect( sortTasks( [ 'css' ] ) ).toEqual( { + before: [], + tasks: [ 'css' ], + after: [] + } ); + } ); + + test( 'returns before tasks only', () => { + expect( sortTasks( [ 'clean' ] ) ).toEqual( { + before: [ 'clean' ], + tasks: [], + after: [] + } ); + } ); + + test( 'specified tasks are ignored', () => { + expect( sortTasks( [ 'watch', 'clean', 'js', 'css' ], [ 'css' ] ) ).toEqual( { + before: [ 'clean' ], + tasks: [ 'js' ], + after: [ 'watch' ] + } ); + } ); + + test( 'specified tasks are ignored', () => { + expect( sortTasks( [ 'watch', 'clean', 'js', 'css' ], [ 'clean' ] ) ).toEqual( { + before: [], + tasks: [ 'js', 'css' ], + after: [ 'watch' ] + } ); + } ); + + test( 'specified tasks are ignored', () => { + expect( sortTasks( [ 'watch', 'clean', 'js', 'css' ], [ 'watch' ] ) ).toEqual( { + before: [ 'clean' ], + tasks: [ 'js', 'css' ], + after: [] + } ); + } ); +} );