@fws/cli
Version:
This CLI is work in progress and it's meant to work together with other Forwardslash boilerplates. Please do not use it if you don't have access to other stuff.
217 lines (187 loc) • 7.63 kB
JavaScript
/**
* W3 Validation
*
* @description Validate domain via W3 API. Currently only supports WP projects that have REST API enabled.
*/
const fancyLog = require('fancy-log');
const colors = require('ansi-colors');
const axios = require('axios');
const validator = require('html-validator');
const helpers = require('../helpers');
const isUrl = require('is-url');
const forOwn = require('lodash.forown');
const cliProgress = require('cli-progress');
const spinner = require('cli-spinner').Spinner;
require('dotenv').config();
// constructor for validator's config object
function ValidatorConfig(url, isLocal) {
this.url = url;
this.format = 'text';
this.ignore = [
'Warning: The "type" attribute is unnecessary for JavaScript resources.',
'Error: Attribute “item-id” not allowed on element “div” at this point.',
'Error: End tag “br”.'
];
// set local validation if run from local env
if (isLocal) {
this.isLocal = isLocal;
}
}
module.exports = {
validateCount: 0,
errorCount: 0,
pageCount: 0,
bar: new cliProgress.SingleBar({}, cliProgress.Presets.shades_classic),
barCount: 0,
spinner: new spinner(colors.yellow('Loading WP REST API... %s')),
errorReport: '',
isLocal: false,
init: function(url) {
this.processStatus(true);
this.isLocal = url === 'local';
const wpURL = this.isLocal ? process.env.VIRTUAL_HOST_URL : url;
// check if passed argument is an url
if (isUrl(wpURL)) {
this.spinner.setSpinnerString('|/-\\');
this.getPostTypes(wpURL);
} else {
this.processStatus();
helpers.consoleLogWarning(`The passed argument '${wpURL}' is not an URL.`, 'red', true);
}
},
getPostTypes: async function(url) {
// get endpoints for all wp post types
try {
this.spinner.start();
const types = await axios.get(`${url}/wp-json/wp/v2/types`);
let filteredTypes = [`${url}/wp-json/wp/v2/pages?per_page=100`];
forOwn(types.data, function(value) {
// exclude unneeded types
if (value.slug !== 'attachment' && value.slug !== 'wp_block' && value.slug !== 'page') {
filteredTypes.push(`${value['_links']['wp:items'][0]['href']}?per_page=10`);
}
});
this.getPages(filteredTypes);
} catch (error) {
if (error.code === 'ENOTFOUND') {
this.processStatus();
helpers.consoleLogWarning(`The URL '${url}' that you are trying to reach is unavailable or wrong.`, 'red', true);
} else {
if (error.response && error.response.status && error.response.statusText) {
helpers.consoleLogWarning(`Error ${error.response.status}: ${error.response.statusText}! \n W3 Validator couldn't scan URL '${url}'. \n Please check if this URL is a WP site and if 'wp-json' routes are avalible.`, 'red', true);
} else {
console.error(error);
this.processStatus();
process.exit(1);
}
}
}
},
getPages: async function(types) {
const _this = this;
let promises = [];
// send request for each rest api endpoint
types.forEach(async (cur) => {
promises.push(axios.get(cur));
});
// wait for all promises to finish
Promise.all(promises).then(function(values) {
const allURLs = values.reduce((agg, value) => {
if (value.data.length > 0) {
value.data.forEach(cur => {
const isPdfUrl = cur.link.includes('spec-sheet-pdf');
if (!isPdfUrl) {
agg.push(cur.link);
}
});
}
return agg;
}, []);
// send filtered urls for validation
_this.spinner.stop();
_this.bar.start(allURLs.length, 0);
_this.checkURLs(allURLs);
}).catch(error => {
console.error(error);
this.processStatus();
helpers.consoleLogWarning(error.message, 'red', true);
});
},
checkURLs: function(URLs) {
// loop through all urls
URLs.forEach((url, i, a) => {
this.validateHTML(url, a);
});
},
validateHTML: async function(url, a) {
// run w3 validator on passed url
const config = new ValidatorConfig(url, this.isLocal);
try {
let counter = 0;
let result = await validator(config);
const hasErrorsString = 'There were errors.';
this.bar.update(++this.barCount);
// check if results have errors
if (result.includes(hasErrorsString)) {
result = result.replace('\n' + hasErrorsString, '').split('\n');
this.errorReport += colors.cyan(`\n\n### W3 Validator Error on Page: ${url}\n\n`);
// count pages that have errors
++this.pageCount;
result.forEach((cur, i) => {
// count errors
++this.errorCount;
if (i % 2 !== 0) {
this.errorReport += colors.yellow(`\n Location: ${cur}\n\n`);
} else {
counter++;
this.errorReport += `${colors.cyan(`--- No.#${counter} --------------------------------------------------------------`)}\n ${colors.red(cur)}`;
}
});
}
// count when last Validator promise is done and stop the script
++this.validateCount;
this.detectProcessEnd(a.length);
} catch (error) {
console.error(error);
this.processStatus();
helpers.consoleLogWarning(error.message, 'red', true);
}
},
detectProcessEnd: function(totalCount) {
if (this.validateCount === totalCount) {
this.validateCount = 0;
if (this.pageCount > 0) {
this.processStatus();
// log error report
console.log(this.errorReport);
const errorMsg = `Found ${this.errorCount} errors, on ${this.pageCount} pages!`;
this.errorCount = 0;
this.pageCount = 0;
// disable process.exit if run from local
helpers.consoleLogWarning(errorMsg, 'red', !this.isLocal);
// show notification if run from local
if (this.isLocal) {
helpers.errorNotify(errorMsg);
}
} else {
helpers.consoleLogWarning('All Good! W3 Validator Passed! :)', 'cyan');
this.processStatus();
// disable process.exit if run from local
if (!this.isLocal) {
process.exit(1);
}
}
}
},
processStatus: function(start = false) {
// label message to know when whole task started and ended
const taskName = 'w3-validator';
const msg = start ? `Starting '${colors.cyan(taskName)}'...` : `Finished '${colors.cyan(taskName)}'`;
if (!start) {
this.spinner.stop();
this.bar.stop();
this.barCount = 0;
}
fancyLog(msg);
}
};