gulp-fabricator
Version:
A multipurpose extension of gulp that allows for simplified and centralized processing and analyzation of Node.js modules and projects.
2,032 lines (1,911 loc) • 50.2 kB
JavaScript
"use strict";
const gulp = require("gulp");
const rename = require("gulp-rename");
const filter = require("gulp-filter");
const concat = require("gulp-concat");
const stripComments = require("gulp-strip-comments");
const sourcemaps = require("gulp-sourcemaps");
const babel = require("gulp-babel");
const uglifyJS = require("uglify-js");
const uglifyComposer = require("gulp-uglify/composer");
const jsdoc = require("gulp-jsdoc3");
const sass = require("gulp-sass");
const sassLint = require("gulp-sass-lint");
const less = require("gulp-less");
const lessHint = require("gulp-lesshint");
const htmlHint = require("gulp-htmlhint");
const cleanCSS = require("gulp-clean-css");
const postCSS = require("gulp-postcss");
const nodemon = require("gulp-nodemon");
const autoprefixer = require("autoprefixer");
const pump = require("pump");
const vinylSourceStream = require("vinyl-source-stream");
const vinylBuffer = require("vinyl-buffer");
const isValidGlob = require("is-valid-glob");
const mergeStream = require("merge-stream");
const through = require("through2");
const fancyLog = require("fancy-log");
const utilities = require("extra-utilities");
const changeCase = require("change-case-bundled");
const path = require("path-extra");
const colors = require("colors");
const util = require("util");
const PluginError = require("plugin-error");
const fabricator = { };
fabricator.log = function logDebug(message) {
return function() {
fancyLog(message);
return through.obj();
}();
};
fabricator.log.info = function logWarning(message) {
return function() {
fancyLog.error(colors.brightBlue("INFO:", message));
return through.obj();
}();
};
fabricator.log.warn = function logWarning(message) {
return function() {
fancyLog.error(colors.brightYellow("WARNING:", message));
return through.obj();
}();
};
fabricator.log.error = function logError(message) {
return function() {
fancyLog.error(colors.red("ERROR:", message));
return through.obj();
}();
};
fabricator.log.files = function logFiles(title) {
let numberOfFiles = 0;
const filePaths = [];
return through.obj(
function(file, encoding, callback) {
if(fabricator.config.debug.verbose) {
if(fabricator.config.debug.logFiles) {
if(fabricator.config.debug.relativeFilePaths) {
filePaths.push(path.relative(fabricator.config.base.directory, file.path));
}
else {
filePaths.push(file.path);
}
}
numberOfFiles++;
}
return callback(null, file);
},
function(callback) {
if(fabricator.config.debug.verbose) {
fancyLog(colors.green((utilities.isNonEmptyString(title) ? title + " " : "") + "File" + (numberOfFiles === 1 ? "" : "s") + " (") + colors.brightBlue(numberOfFiles) + colors.green("):"));
for(let i = 0; i < filePaths.length; i++) {
const filePath = filePaths[i];
fancyLog(" - ".green + filePath.brightBlue);
}
}
return callback();
}
);
};
fabricator.src = function src(glob, options) {
return gulp.src(isValidGlob(glob) ? glob : " ", utilities.merge({ allowEmpty: true }, options));
};
fabricator.noop = function noop() {
return through.obj();
};
fabricator.noopTask = function noopTask(callback) {
return callback();
};
function tasksOrNoop(tasks, executionType) {
if(executionType !== "series" && executionType !== "parallel") {
throw new Error("Invalid execution type, expected 'series' or 'parallel'.");
}
if(utilities.isNonEmptyString(tasks)) {
tasks = [tasks];
}
return utilities.isEmptyArray(tasks) ? fabricator.noopTask : gulp[executionType](...tasks);
}
fabricator.seriesTasks = function seriesTasks(tasks) {
return tasksOrNoop(tasks, "series");
};
fabricator.parallelTasks = function parallelTasks(callback) {
return tasksOrNoop(tasks, "parallel");
};
fabricator.setup = function setup(options) {
options = fabricator.formatOptions(options);
fabricator.config = options;
if(fabricator.config.debug.logConfig) {
console.log("[".magenta + "Fabricator Configuration".brightBlue + "]".magenta);
console.log(util.inspect(
fabricator.config,
{
depth: Infinity,
maxArrayLength: Infinity,
showHidden: false,
getters: true,
colors: true
}
));
console.log("");
}
function namespace(value) {
if(utilities.isEmptyString(value)) {
return value;
}
return (utilities.isEmptyString(options.namespace) ? "" : options.namespace + ":") + value.trim().replace(/^[:]+/, "");
}
const tasks = {
build: [],
lint: [],
docs: [],
web: [],
watch: [],
default: []
};
if(options.build.tasks.includes("js")) {
if(options.build.enabled) {
gulp.task(namespace("build:js"), function(callback) {
pump([
fabricator.src(options.js.source).on("error", fancyLog.error)
.pipe(fabricator.log.files("JavaScript Source"))
.pipe(options.build.bundle
? concat((utilities.isEmptyString(options.build.prefix) ? "" : options.build.prefix) + options.build.fileName + ".js").on("error", fancyLog.error)
: fabricator.noop())
.pipe(options.type === "module"
? fabricator.transform({
transformation: options.build.transformation,
name: options.build.exportName
}).on("error", fancyLog.error)
: fabricator.noop())
.pipe(options.build.stripComments && options.js.stripComments.enabled
? stripComments(options.js.stripComments.options).on("error", fancyLog.error)
: fabricator.noop())
.pipe(options.js.sourcemaps.enabled && options.js.sourcemaps.postCompile
? sourcemaps.init().on("error", fancyLog.error)
: fabricator.noop())
.pipe(options.js.babel.enabled
? babel({
presets: options.js.babel.presets
}).on("error", fancyLog.error)
: fabricator.noop())
.pipe(options.js.sourcemaps.enabled && options.js.sourcemaps.postCompile
? sourcemaps.write(options.js.sourcemaps.embed
? undefined
: options.js.sourcemaps.destination).on("error", fancyLog.error)
: fabricator.noop())
.pipe(gulp.dest(options.js.destination).on("error", fancyLog.error))
.pipe(options.js.sourcemaps.enabled && options.js.sourcemaps.postCompile
? filter(function(file) {
const fileName = utilities.getFileName(file.path);
if(utilities.isNonEmptyString(fileName) && fileName.indexOf(".map") !== -1) {
return false;
}
return true;
}).on("error", fancyLog.error)
: fabricator.noop())
.pipe(fabricator.log.files("JavaScript Output"))
.pipe(options.js.minify
? rename({ extname: ".min.js" }).on("error", fancyLog.error)
: fabricator.noop())
.pipe(options.js.minify && options.js.sourcemaps.enabled && options.js.sourcemaps.postMinify
? sourcemaps.init().on("error", fancyLog.error)
: fabricator.noop()),
options.js.minify
? uglifyComposer(uglifyJS, console)({ mangle: false }).on("error", fancyLog.error)
: fabricator.noop(),
fabricator.noop()
.pipe(options.js.minify && options.js.sourcemaps.enabled && options.js.sourcemaps.postMinify
? sourcemaps.write(options.js.sourcemaps.embed
? undefined
: options.js.sourcemaps.destination).on("error", fancyLog.error)
: fabricator.noop())
.pipe(options.js.minify ? fabricator.log.files("JavaScript Minified Output") : fabricator.noop()),
options.js.minify
? gulp.dest(options.js.destination).on("error", fancyLog.error)
: fabricator.noop()
], callback);
});
tasks.build.push(namespace("build:js"));
}
if(options.lint.enabled && options.js.lint.enabled) {
gulp.task(namespace("lint:js"), function(callback) {
fancyLog.warn("WARNING: JavaScript linting is not currently supported!".brightYellow);
return callback();
});
tasks.lint.push(namespace("lint:js"));
}
if(options.js.docs.enabled) {
gulp.task(namespace("docs:js"), function(callback) {
fabricator.src(options.js.source, { read: false }).on("error", fancyLog.error)
.pipe(fabricator.log.files("Documentation Source"))
.pipe(jsdoc(utilities.merge(options.js.docs.config, { opts: { destination: options.js.docs.destination } }), callback).on("error", fancyLog.error));
});
tasks.docs.push(namespace("docs:js"));
if(options.js.docs.nojekyll) {
gulp.task(namespace("docs:nojekyll"), function(callback) {
const stream = vinylSourceStream(".nojekyll");
stream.write("");
process.nextTick(function() {
stream.end();
});
stream.pipe(vinylBuffer())
.pipe(fabricator.log.files("No Jekyll"))
.pipe(gulp.dest(options.js.docs.destination))
.on("end", callback);
});
tasks.docs.push(namespace("docs:nojekyll"));
}
}
if(options.lint.enabled && options.html.lint.enabled) {
gulp.task(namespace("lint:html"), function() {
fabricator.src(options.html.source).on("error", fancyLog.error)
.pipe(fabricator.log.files("HTML"))
.pipe(htmlHint(options.html.lint.options).on("error", fancyLog.error))
.pipe(htmlHint.reporter().on("error", fancyLog.error));
});
tasks.lint.push(namespace("lint:html"));
}
}
if(options.build.tasks.includes("scss") || options.build.tasks.includes("less") || options.build.tasks.includes("css")) {
if(options.build.enabled) {
gulp.task(namespace("build:css"), function(callback) {
mergeStream(
fabricator.src(options.css.source).on("error", fancyLog.error)
.pipe(fabricator.log.files("CSS Source")),
options.build.tasks.includes("scss")
? gulp.src(options.scss.source).on("error", fancyLog.error)
.pipe(fabricator.log.files("SCSS/SASS Source"))
.pipe(options.build.stripComments && options.scss.stripComments.enabled
? stripComments(options.scss.stripComments.options).on("error", fancyLog.error)
: fabricator.noop())
.pipe(sass().on("error", sass.logError))
: fabricator.src([]),
options.build.tasks.includes("less")
? fabricator.src(options.less.source).on("error", fancyLog.error)
.pipe(fabricator.log.files("LESS Source"))
.pipe(options.build.stripComments && options.less.stripComments.enabled
? stripComments(options.less.stripComments.options).on("error", fancyLog.error)
: fabricator.noop())
.pipe(less())
: fabricator.src([]).on("error", fancyLog.error))
.pipe(rename({ prefix: utilities.isEmptyString(options.build.prefix) ? "" : options.build.prefix }).on("error", fancyLog.error))
.pipe(options.css.autoprefixer.enabled
? postCSS([autoprefixer(options.css.autoprefixer.options)]).on("error", fancyLog.error)
: fabricator.noop())
.pipe(options.build.stripComments && options.css.stripComments.enabled
? stripComments(options.css.stripComments.options).on("error", fancyLog.error)
: fabricator.noop())
.pipe(fabricator.log.files("CSS Output"))
.pipe(gulp.dest(options.css.destination).on("error", fancyLog.error))
.pipe(options.css.minify
? cleanCSS().on("error", fancyLog.error)
: fabricator.noop())
.pipe(options.css.minify
? rename({ extname: ".min.css" }).on("error", fancyLog.error)
: fabricator.noop())
.pipe(options.css.minify ? fabricator.log.files("CSS Minified Output") : fabricator.noop())
.pipe(options.css.minify
? gulp.dest(options.css.destination).on("error", fancyLog.error)
: fabricator.noop())
.on("end", callback);
});
tasks.build.push(namespace("build:css"));
}
if(options.lint.enabled && options.scss.lint.enabled) {
gulp.task(namespace("lint:scss"), function() {
fabricator.src(options.scss.source).on("error", fancyLog.error)
.pipe(fabricator.log.files("SCSS/SASS Source"))
.pipe(sassLint(options.scss.lint))
.pipe(sassLint.format())
.pipe(sassLint.failOnError());
});
tasks.lint.push(namespace("lint:scss"));
}
if(options.lint.enabled && options.less.lint.enabled) {
gulp.task(namespace("lint:less"), function() {
fabricator.src(options.less.source).on("error", fancyLog.error)
.pipe(fabricator.log.files("LESS Source"))
.pipe(lessHint(options.less.lint.options))
.pipe(lessHint.reporter(options.less.lint.reporter))
.pipe(lessHint.failOnError());
});
tasks.lint.push(namespace("lint:less"));
}
}
if(options.type === "website") {
if(options.web.nodemon) {
gulp.task(namespace("web:nodemon"), function(callback) {
nodemon(options.web.nodemon.config);
return callback();
});
tasks.web.push(namespace("web:nodemon"));
}
}
gulp.task(namespace("watch:main"), function(callback) {
if(options.build.tasks.includes("js") && options.build.enabled) {
gulp.watch(options.js.source, fabricator.seriesTasks(namespace("build:js")));
}
if(options.build.tasks.includes("scss") && options.build.enabled) {
gulp.watch(options.scss.source, fabricator.seriesTasks(namespace("build:css")));
}
if(options.build.tasks.includes("less") && options.build.enabled) {
gulp.watch(options.less.source, fabricator.seriesTasks(namespace("build:css")));
}
if(options.build.tasks.includes("css") && options.build.enabled) {
gulp.watch(options.css.source, fabricator.seriesTasks(namespace("build:css")));
}
return callback();
});
tasks.watch.push(namespace("watch:main"));
if(options.build.watch) {
tasks.default.push("watch");
}
tasks.default.push(namespace("build"), namespace("lint"), namespace("docs"));
if(options.type === "website") {
tasks.default.push(namespace("web"));
}
const additionalTaskTypes = Object.keys(options.additionalTasks);
for(let i = 0; i < additionalTaskTypes.length; i++) {
const additionalTaskType = additionalTaskTypes[i];
tasks[additionalTaskType].push(...options.additionalTasks[additionalTaskType]);
}
gulp.task(namespace("build"), fabricator.seriesTasks(tasks.build));
gulp.task(namespace("lint"), fabricator.seriesTasks(tasks.lint));
gulp.task(namespace("docs"), fabricator.seriesTasks(tasks.docs));
gulp.task(namespace("web"), fabricator.seriesTasks(tasks.web));
gulp.task(namespace("watch"), fabricator.seriesTasks(tasks.watch));
gulp.task(namespace("default"), fabricator.seriesTasks(tasks.default));
};
fabricator.formatOptions = function formatOptions(options) {
return utilities.formatValue(
options,
{
type: "object",
strict: true,
autopopulate: true,
removeExtra: true,
nonEmpty: true,
required: true,
formatter: function(value, format, options) {
if(utilities.isEmptyString(value.build.fileName)) {
value.build.fileName = changeCase.paramCase(value.name);
}
if(utilities.isEmptyString(value.build.exportName)) {
value.build.exportName = changeCase.camelCase(value.name);
}
for(let fileType in ["js", "html", "scss", "less", "css"]) {
const config = value[fileType];
for(let locationType in ["source, destination"]) {
const baseLocation = value.base[locationType];
if(utilities.isEmptyString(baseLocation)) {
continue;
}
const location = config[locationType];
if(utilities.isNonEmptyString(location)) {
config[locationType] = path.join(baseLocation, location);
}
else if(utilities.isNonEmptyArray()) {
const formattedLocation = [];
for(let i = 0; i < location.length; i++) {
formattedLocation.push(path.join(baseLocation, location[i]));
}
config[locationType] = formattedLocation;
}
}
}
if(utilities.isEmptyString(value.repository)) {
try {
const packageFile = require(path.join(value.base.directory, "package.json"));
if(utilities.isObjectStrict(packageFile)) {
if(utilities.isObjectStrict(packageFile.repository)) {
if(utilities.isNonEmptyString(packageFile.repository.url)) {
value.repository = packageFile.repository.url;
}
}
else if(utilities.isNonEmptyString(packageFile.repository)) {
value.repository = packageFile.repository;
}
}
if(utilities.isEmptyString(value.repository)) {
console.warn("WARNING: Module package file is invalid or missing repository path.".brightYellow);
}
}
catch(error) {
console.warn("WARNING: Could not locate module package file.".brightYellow);
}
}
if(utilities.isNonEmptyString(value.repository)) {
value.repository = value.repository.replace(/^git\+/, "").replace(/\.git$/, "");
if(!utilities.isObjectStrict(value.js.docs.config.docdash.menu)) {
value.js.docs.config.docdash.menu = { };
}
if(!utilities.isObjectStrict(value.js.docs.config.docdash.menu.Repository)) {
value.js.docs.config.docdash.menu.Repository = { };
}
if(utilities.isEmptyString(value.js.docs.config.docdash.menu.Repository.href)) {
value.js.docs.config.docdash.menu.Repository.href = value.repository;
}
}
if(value.type === "website") {
try {
const nodemonConfigFile = require(path.join(value.base.directory, "nodemon.json"));
if(utilities.isObjectStrict(nodemonConfigFile)) {
value.web.nodemon.config = utilities.merge(value.web.nodemon.config, nodemonConfigFile);
}
else {
console.warn("WARNING: Nodemon configuration file is empty or malformed.".brightYellow);
}
}
catch(error) {
console.warn("WARNING: Could not locate nodemon configuration file.".brightYellow);
}
}
if(!utilities.isBoolean(value.web.nodemon.config.verbose) || (!value.web.nodemon.config.verbose && value.debug.verbose)) {
value.web.nodemon.config.verbose = value.debug.verbose;
}
return value;
},
format: {
name: {
type: "string",
case: "title",
trim: true,
nonEmpty: true,
required: true
},
type: {
type: "string",
case: "lower",
trim: true,
nonEmpty: true,
default: "module",
formatter: function(value, format, options) {
if(value !== "module" && value !== "website") {
throw new Error("Invalid or unsupported module type - expected Module or Website, received: \"" + value + "\".");
}
return value;
}
},
debug: {
type: "object",
strict: true,
autopopulate: true,
removeExtra: true,
format: {
verbose: {
type: "boolean",
default: process.argv.includes("--debug") || process.argv.includes("--verbose")
},
logConfig: {
type: "boolean",
default: process.argv.includes("--debug")
},
logFiles: {
type: "boolean",
default: true
},
relativeFilePaths: {
type: "boolean",
default: true
}
}
},
repository: {
type: "string",
trim: true,
nonEmpty: true
},
web: {
type: "object",
strict: true,
autopopulate: true,
removeExtra: true,
format: {
nodemon: {
type: "object",
strict: true,
autopopulate: true,
removeExtra: true,
format: {
enabled: {
type: "boolean",
default: true
},
config: {
type: "object",
strict: true,
autopopulate: true,
removeExtra: true,
format: {
verbose: {
type: "boolean"
},
restartable: {
type: "string",
trim: true,
nonEmpty: true,
default: "rs"
},
ignore: {
type: "array",
nonEmpty: true,
default: [
".git",
"node_modules/**/node_modules",
"views",
"scss",
"sass",
"less",
"public"
],
format: {
type: "string",
trim: true,
nonEmpty: true
}
},
execMap: {
type: "object",
strict: true,
autopopulate: true,
removeExtra: true,
format: {
js: {
type: "string",
trim: true,
nonEmpty: true,
default: "node"
}
}
},
watch: {
type: "array",
nonEmpty: true,
default: [".", "website/"],
format: {
type: "string",
trim: true,
nonEmpty: true
}
},
ext: {
type: "string",
trim: true,
nonEmpty: true,
default: "js,json"
}
}
}
}
}
}
},
js: {
type: "object",
strict: true,
autopopulate: true,
removeExtra: true,
format: {
source: {
type: "array",
default: ["src/**/*.js"],
format: {
type: "string",
trim: true,
nonEmpty: true
}
},
destination: {
type: "string",
trim: true,
nonEmpty: true,
default: "dist/"
},
bundle: {
type: "boolean",
default: true
},
stripComments: {
type: "object",
strict: true,
autopopulate: true,
removeExtra: true,
format: {
enabled: {
type: "boolean",
default: true
},
options: {
type: "object",
strict: true,
autopopulate: true,
format: {
space: {
type: "boolean",
default: false
},
trim: {
type: "boolean",
default: true
}
}
}
}
},
babel: {
type: "object",
strict: true,
autopopulate: true,
removeExtra: true,
format: {
enabled: {
type: "boolean",
default: false
},
presets: {
type: "array",
nonEmpty: true,
default: ["@babel/env"],
format: {
type: "string",
trim: true,
nonEmpty: true
}
}
}
},
sourcemaps: {
type: "object",
strict: true,
autopopulate: true,
removeExtra: true,
format: {
enabled: {
type: "boolean",
default: true
},
embed: {
type: "boolean",
default: false
},
destination: {
type: "string",
default: "."
},
postCompile: {
type: "boolean",
default: true
},
postMinify: {
type: "boolean",
default: true
}
}
},
minify: {
type: "boolean",
default: true
},
lint: {
type: "object",
strict: true,
autopopulate: true,
removeExtra: true,
format: {
enabled: {
type: "boolean",
default: true
}
}
},
docs: {
type: "object",
strict: true,
autopopulate: true,
removeExtra: true,
format: {
enabled: {
type: "boolean",
default: true
},
destination: {
type: "string",
trim: true,
nonEmpty: true,
case: "lower",
default: "docs/"
},
repository: {
type: "boolean",
default: true
},
nojekyll: {
type: "boolean",
default: true
},
config: {
type: "object",
strict: true,
autopopulate: true,
format: {
opts: {
type: "object",
strict: true,
autopopulate: true,
format: {
template: {
type: "string",
trim: true,
nonEmpty: true,
default: "node_modules/docdash"
},
readme: {
type: "string",
trim: true,
nonEmpty: true,
default: "README.md"
}
}
},
plugins: {
type: "array",
nonEmpty: true,
default: [
"plugins/markdown"
],
format: {
type: "string",
trim: true,
nonEmpty: true
}
},
docdash: {
type: "object",
strict: true,
autopopulate: true,
format: {
search: {
type: "boolean",
default: true
}
}
}
}
}
}
}
}
},
html: {
type: "object",
strict: true,
autopopulate: true,
removeExtra: true,
format: {
source: {
type: "array",
default: ["src/**/*.html"],
format: {
type: "string",
trim: true,
nonEmpty: true
}
},
stripComments: {
type: "object",
strict: true,
autopopulate: true,
removeExtra: true,
format: {
enabled: {
type: "boolean",
default: true
},
options: {
type: "object",
strict: true,
autopopulate: true,
format: {
space: {
type: "boolean",
default: false
},
trim: {
type: "boolean",
default: true
}
}
}
}
},
lint: {
type: "object",
strict: true,
removeExtra: true,
autopopulate: true,
format: {
enabled: {
type: "boolean",
default: false
},
options: {
type: "object",
strict: true,
autopopulate: true,
format: {
"tagname-lowercase": {
type: "boolean",
default: true
},
"attr-lowercase": {
type: "boolean",
default: true
},
"attr-value-double-quotes": {
type: "boolean",
default: true
},
"attr-no-duplication": {
type: "boolean",
default: true
},
"doctype-first": {
type: "boolean",
default: false
},
"tag-pair": {
type: "boolean",
default: true
},
"spec-char-escape": {
type: "boolean",
default: true
},
"id-unique": {
type: "boolean",
default: true
},
"src-not-empty": {
type: "boolean",
default: true
},
"alt-require": {
type: "boolean",
default: true
},
"id-class-value": {
type: "string",
case: "lower",
trim: true,
nonEmpty: true,
default: "dash",
validator: function(value, format, options) {
if(value !== "underline" && value !== "dash" && value !== "hump") {
throw new Error("Invalid html hint id-class-value option: \"" + value + "\" - expected underline, dash or hump.");
}
return true;
}
},
"style-disabled": {
type: "boolean",
default: true
},
"inline-style-disabled": {
type: "boolean",
default: true
},
"inline-script-disabled": {
type: "boolean",
default: true
},
"space-tab-mixed-disabled": {
type: "boolean",
default: true
},
"id-class-ad-disabled": {
type: "boolean",
default: true
},
"attr-unsafe-chars": {
type: "boolean",
default: true
}
}
}
}
}
}
},
scss: {
type: "object",
strict: true,
autopopulate: true,
removeExtra: true,
format: {
source: {
type: "array",
default: ["scss/**/*.scss", "scss/**/*.sass"],
format: {
type: "string",
trim: true,
nonEmpty: true
}
},
stripComments: {
type: "object",
strict: true,
autopopulate: true,
removeExtra: true,
format: {
enabled: {
type: "boolean",
default: true
},
options: {
type: "object",
strict: true,
autopopulate: true,
format: {
ignore: {
type: "regex",
default: /url\([\w\s:\/=\-\+;,]*\)/g
},
space: {
type: "boolean",
default: false
},
trim: {
type: "boolean",
default: true
}
}
}
}
},
lint: {
type: "object",
strict: true,
autopopulate: true,
removeExtra: true,
format: {
enabled: {
type: "boolean",
default: true
},
options: {
type: "object",
strict: true,
autopopulate: true,
format: {
formatter: {
type: "string",
case: "lower",
trim: true,
nonEmpty: true,
default: "stylish"
}
}
},
rules: {
type: "object",
strict: true,
autopopulate: true,
format: {
"attribute-quotes": {
type: "array",
nonEmpty: true,
default: [
1,
{ include: true }
]
},
"border-zero": {
type: "array",
nonEmpty: true,
default: [
1,
{ convention: "none" }
]
},
"brace-style": {
type: "array",
nonEmpty: true,
default: [
1,
{
style: "stroustrup",
"allow-single-line": true
}
]
},
"class-name-format": {
type: "array",
nonEmpty: true,
default: [
1,
{
"allow-leading-underscore": false,
convention: "hyphenatedlowercase"
}
]
},
"clean-import-paths": {
type: "integer",
default: 0
},
"declarations-before-nesting": {
type: "integer",
default: 1
},
"empty-args": {
type: "array",
nonEmpty: true,
default: [
1,
{ include: false }
]
},
"empty-line-between-blocks": {
type: "array",
nonEmpty: true,
default: [
1,
{
include: true,
"allow-single-line-rulesets": true
}
]
},
"extends-before-declarations": {
type: "integer",
default: 1
},
"extends-before-mixins": {
type: "integer",
default: 1
},
"final-newline": {
type: "array",
nonEmpty: true,
default: [
1,
{ include: true }
]
},
"force-attribute-nesting": {
type: "integer",
default: 0
},
"force-element-nesting": {
type: "integer",
default: 0
},
"force-pseudo-nesting": {
type: "integer",
default: 0
},
"function-name-format": {
type: "array",
nonEmpty: true,
default: [
1,
{
"allow-leading-underscore": false,
convention: "hyphenatedlowercase"
}
]
},
"hex-length": {
type: "array",
nonEmpty: true,
default: [
1,
{ style: "long" }
]
},
"hex-notation": {
type: "array",
nonEmpty: true,
default: [
1,
{ style: "uppercase" }
]
},
"id-name-format": {
type: "array",
nonEmpty: true,
default: [
1,
{
"allow-leading-underscore": false,
convention: "hyphenatedlowercase"
}
]
},
indentation: {
type: "array",
nonEmpty: true,
default: [
1,
{ size: "tab" }
]
},
"leading-zero": {
type: "array",
nonEmpty: true,
default: [
1,
{ include: true }
]
},
"mixin-name-format": {
type: "array",
nonEmpty: true,
default: [
1,
{
"allow-leading-underscore": false,
convention: "hyphenatedlowercase"
}
]
},
"nesting-depth": {
type: "integer",
default: 0
},
"no-color-keywords": {
type: "integer",
default: 0
},
"no-color-literals": {
type: "integer",
default: 0
},
"no-duplicate-properties": {
type: "array",
nonEmpty: true,
default: [
1,
[]
]
},
"no-empty-rulesets": {
type: "integer",
default: 0
},
"no-important": {
type: "integer",
default: 0
},
"no-invalid-hex": {
type: "integer",
default: 1
},
"no-misspelled-properties": {
type: "array",
nonEmpty: true,
default: [
1,
{
"extra-properties": [
"border-radius-topleft",
"border-radius-topright",
"border-radius-bottomright",
"border-radius-bottomleft",
"flex-order",
"flex-positive",
"flex-negative",
"flex-pack",
"flex-line-pack",
"flex-align",
"flex-item-align",
"flex-preferred-size"
]
}
]
},
"no-qualifying-elements": {
type: "array",
nonEmpty: true,
default: [
1,
{
"allow-element-with-attribute": true,
"allow-element-with-class": true,
"allow-element-with-id": true
}
]
},
"no-trailing-whitespace": {
type: "integer",
default: 1
},
"no-trailing-zero": {
type: "integer",
default: 1
},
"no-url-domains": {
type: "integer",
default: 1
},
"no-url-protocols": {
type: "array",
nonEmpty: true,
default: [
1,
{ "allow-protocol-relative-urls": false }
]
},
"no-vendor-prefixes": {
type: "integer",
default: 0
},
"one-declaration-per-line": {
type: "integer",
default: 1
},
"placeholder-name-format": {
type: "array",
nonEmpty: true,
default: [
1,
{
"allow-leading-underscore": false,
convention: "hyphenatedlowercase"
}
]
},
"property-sort-order": {
type: "integer",
default: 0
},
quotes: {
type: "array",
nonEmpty: true,
default: [
1,
{ style: "double" }
]
},
"single-line-per-selector": {
type: "integer",
default: 1
},
"space-after-bang": {
type: "array",
nonEmpty: true,
default: [
1,
{ include: false }
]
},
"space-after-colon": {
type: "array",
nonEmpty: true,
default: [
1,
{ include: true }
]
},
"space-after-comma": {
type: "array",
nonEmpty: true,
default: [
1,
{ include: true }
]
},
"space-around-operator": {
type: "array",
nonEmpty: true,
default: [
1,
{ include: true }
]
},
"space-before-bang": {
type: "array",
nonEmpty: true,
default: [
1,
{ include: true }
]
},
"space-before-brace": {
type: "array",
nonEmpty: true,
default: [
1,
{ include: true }
]
},
"space-before-colon": {
type: "array",
nonEmpty: true,
default: [
1,
{ include: false }
]
},
"space-between-parens": {
type: "array",
nonEmpty: true,
default: [
1,
{ include: false }
]
},
"trailing-semicolon": {
type: "array",
nonEmpty: true,
default: [
1,
{ include: true }
]
},
"url-quotes": {
type: "integer",
default: 1
},
"variable-name-format": {
type: "array",
nonEmpty: true,
default: [
1,
{
"allow-leading-underscore": false,
convention: "hyphenatedlowercase"
}
]
},
"zero-unit": {
type: "array",
nonEmpty: true,
default: [
0,
{ include: true }
]
}
}
}
}
}
}
},
less: {
type: "object",
strict: true,
autopopulate: true,
removeExtra: true,
format: {
source: {
type: "array",
default: ["less/**/*.less"],
format: {
type: "string",
trim: true,
nonEmpty: true
}
},
stripComments: {
type: "object",
strict: true,
autopopulate: true,
removeExtra: true,
format: {
enabled: {
type: "boolean",
default: true
},
options: {
type: "object",
strict: true,
autopopulate: true,
format: {
ignore: {
type: "regex",
default: /url\([\w\s:\/=\-\+;,]*\)/g
},
space: {
type: "boolean",
default: false
},
trim: {
type: "boolean",
default: true
}
}
}
}
},
lint: {
type: "object",
strict: true,
autopopulate: true,
removeExtra: true,
format: {
enabled: {
type: "boolean",
default: true
},
reporter: {
type: "string",
trim: true,
nonEmpty: true,
default: "stylish"
},
options: {
type: "object",
strict: true,
autopopulate: true,
format: {
maxWarnings: {
type: "integer",
validator: function(value, format, options) {
if(!Number.isInteger(value)) {
return true;
}
return value >= 0;
}
}
}
}
}
}
}
},
css: {
type: "object",
strict: true,
autopopulate: true,
removeExtra: true,
format: {
source: {
type: "array",
default: [],
format: {
type: "string",
trim: true,
nonEmpty: true
}
},
destination: {
type: "string",
trim: true,
nonEmpty: true,
default: "dist/"
},
autoprefixer: {
type: "object",
strict: true,
autopopulate: true,
removeExtra: true,
format: {
enabled: {
type: "boolean",
default: true
},
options: {
type: "object",
strict: true,
autopopulate: true,
default: { }
}
}
},
stripComments: {
type: "object",
strict: true,
autopopulate: true,
removeExtra: true,
format: {
enabled: {
type: "boolean",
default: true
},
options: {
type: "object",
strict: true,
autopopulate: true,
format: {
ignore: {
type: "regex",
default: /url\([\w\s:\/=\-\+;,]*\)/g
},
space: {
type: "boolean",
default: false
},
trim: {
type: "boolean",
default: true
}
}
}
}
},
minify: {
type: "boolean",
default: true
}
}
},
base: {
type: "object",
strict: true,
autopopulate: true,
removeExtra: true,
format: {
directory: {
type: "string",
trim: true,
nonEmpty: true,
required: true,
default: process.cwd()
},
source: {
type: "string",
trim: true,
nonEmpty: true
},
destination: {
type: "string",
trim: true,
nonEmpty: true
}
}
},
build: {
type: "object",
strict: true,
autopopulate: true,
removeExtra: true,
format: {
enabled: {
type: "boolean",
default: true
},
fileName: {
type: "string",
case: "param",
trim: true,
nonEmpty: true
},
exportName: {
type: "string",
case: "camel",
trim: true,
nonEmpty: true
},
transformation: {
type: "string",
case: "lower",
trim: true,
nonEmpty: true,
default: "umd",
formatter: function(value, format, options) {
if(utilities.isEmptyString(value) || value === "none" || value === "disabled") {
return null;
}
if(value !== "umd") {
throw new Error("Invalid or unsupported module transformation - expected UMD, received: \"" + value + "\".");
}
return value;
}
},
namespace: {
type: "string",
case: "lower",
trim: true,
nonEmpty: true,
formatter: function(value, format, options) {
if(utilities.isEmptyString(value)) {
return value;
}
return value.replace(/[:]+$/, "");
}
},
tasks: {
type: "array",
default: ["js"],
format: {
type: "string",
case: "lower",
trim: true,
nonEmpty: true,
formatter: function(value, format, options) {
if(value === "javascript") {
value = "js";
}
if(value === "sass") {
value = "scss";
}
if(value !== "js" && value !== "scss" && value !== "less" && value !== "css") {
throw new Error("Invalid or unsupported task: \"" + value + "\" - expected javascript, js, scss, sass, less or css.");
}
return value;
}
}
},
prefix: {
type: "string",
trim: true,
nonEmpty: true
},
stripComments: {
type: "boolean",
default: true
},
watch: {
type: "boolean",
default: true
}
}
},
lint: {
type: "object",
strict: true,
autopopulate: true,
removeExtra: true,
format: {
enabled: {
type: "boolean",
default: true
}
}
},
additionalTasks: {
type: "object",
strict: true,
autopopulate: true,
removeExtra: true,
format: {
build: {
type: "array",
default: [],
format: {
type: "string",
trim: true,
nonEmpty: true
}
},
lint: {
type: "array",
default: [],
format: {
type: "string",
trim: true,
nonEmpty: true
}
},
watch: {
type: "array",
default: [],
format: {
type: "string",
trim: true,
nonEmpty: true
}
},
default: {
type: "array",
default: [],
format: {
type: "string",
trim: true,
nonEmpty: true
}
}
}
}
}
},
{
throwErrors: true,
verbose: true
}
);
};
function getContent(buffer) {
if(utilities.isObject(buffer) && utilities.isValid(buffer.contents)) {
return buffer.contents.toString();
}
else if(buffer instanceof Buffer) {
return buffer.toString();
}
else if(typeof buffer === "string") {
return buffer;
}
throw new Error("Could not retrieve content.");
}
function updateContent(buffer, content, encoding) {
if(utilities.isObject(buffer) && utilities.isValid(buffer.contents)) {
buffer.contents = new Buffer(content, encoding);
return buffer;
}
else if(buffer instanceof Buffer) {
return new Buffer(content, encoding);
}
return content;
}
const format = {
transformation: {
type: "object",
strict: true,
removeExtra: true,
autopopulate: true,
format: {
encoding: {
type: "string",
trim: true,
nonEmpty: true,
default: "utf8"
},
indentation: {
type: "string",
trim: true,
nonEmpty: true,
default: "\t"
},
newLine: {
type: "string",
trim: true,
nonEmpty: true,
default: "\n"
}
}
},
transform: {
type: "object",
strict: true,
autopopulate: true,
format: {
transformation: {
type: "string",
case: "camel",
trim: true,
nullable: true,
validator: function(value, format, options) {
if(utilities.isEmptyString(value) || value === "none") {
return true;
}
return Object.keys(fabricator.transformation).includes(value);
}
}
}
}
};
function replaceModuleExportWithReturn(text) {
if(typeof text !== "string") {
return null;
}
return text.replace("module.exports =", "return");
};
fabricator.transformation = { };
fabricator.transformation.indentText = function indentText(buffer, options) {
options = utilities.formatValue(
options,
utilities.merge(
format.transformation,
{
format: {
amount: {
type: "integer",
default: 1,
validator: function(value, format, options) {
return value >= 0;
}
},
clearEmptyLines: {
type: "boolean",
default: true
}
}
}
),
{
throwErrors: true
}
);
return updateContent(
buffer,
utilities.indentText(getContent(buffer), options.amount, options.indentation, options.clearEmptyLines),
options.encoding
);
};
fabricator.transformation.trimWhitespace = function trimWhitespace(buffer, options) {
options = utilities.formatValue(
options,
utilities.merge(
format.transformation,
{
format: {
trimNewlines: {
type: "boolean",
default: false
}
}
}
),
{
throwErrors: true
}
);
return updateContent(
buffer,
utilities.trimWhitespace(getContent(buffer), options.trimNewlines),
options.encoding
);
};
fabricator.transformation.iife = function iife(buffer, options) {
options = utilities.formatValue(
options,
format.transformation,
{
throwErrors: true
}
);
return updateContent(
buffer,
"(function() {" + options.newLine +
options.newLine +
utilities.trimTrailingNewlines(utilities.indentText(getContent(buffer), 1, options.indentation)) + options.newLine +
options.newLine +
"})();" + options.newLine,
options.encoding
);
};
fabricator.transformation.umd = function umd(buffer, options) {
options = utilities.formatValue(
options,
utilities.merge(
format.transformation,
{
format: {
name: {
type: "string",
case: "camel",
trim: true,
nonEmpty: true,
required: true
}
}
}
),
{
throwErrors: true
}
);
return updateContent(
buffer,
"(function(global, factory) {" + options.newLine +
options.indentation + "typeof exports === \"object\" && typeof module !== \"undefined\" ? module.exports = factory() :" + options.newLine +
options.indentation + "typeof define === \"function\" && define.amd ? define(factory) :" + options.newLine +
options.indentation + "(global." + options.name + " = factory());" + options.newLine +
"} (this, function() {" + options.newLine +
options.newLine +
utilities.trimTrailingNewlines(utilities.indentText(replaceModuleExportWithReturn(getContent(buffer)), 1, options.indentation)) + options.newLine +
options.newLine +
"}));" + options.newLine,
options.encoding
);
};
fabricator.transform = function transform(options) {
options = utilities.formatValue(
options,
format.transform,
{
throwErrors: true
}
);
return through.obj(function(file, encoding, callback) {
if(file.isNull()) {
return callback(null, file);
}
if(file.isStream()) {
return callback(new PluginError("fabricator", "Streaming is not supported."));
}
if(utilities.isNonEmptyString(options.transformation) && utilities.isFunction(fabricator.transformation[options.transformation])) {
try {
fabricator.transformation[options.transformation](
file,
utilities.merge(