UNPKG

@forwardslashns/fws-cli

Version:

CLI meant to work together with other Forwardslash boilerplates.

166 lines (132 loc) 6.92 kB
//! w3c-html-validator v1.6.4 ~~ https://github.com/center-key/w3c-html-validator ~~ MIT License import fs from 'fs'; import log from 'fancy-log'; import request from 'superagent'; // eslint-disable-next-line import slash from 'slash'; import colors from 'ansi-colors'; const w3cHtmlValidator = { version: '1.6.4', async validate(options) { const defaults = { checkUrl: 'https://validator.w3.org/nu/', ignoreLevel: null, ignoreMessages: [], output: 'json', }; const settings = { ...defaults, ...options }; if (!settings.html && !settings.filename && !settings.website) throw Error('[w3c-html-validator] Must specify the "html", "filename", or "website" option.'); if (![null, 'info', 'warning'].includes(settings.ignoreLevel)) throw Error('[w3c-html-validator] Invalid ignoreLevel option: ' + settings.ignoreLevel); if (settings.output !== 'json' && settings.output !== 'html') throw Error('[w3c-html-validator] Option "output" must be "json" or "html".'); const filename = settings.filename ? slash(settings.filename) : null; const mode = settings.html ? 'html' : filename ? 'filename' : 'website'; const readFile = (filename) => fs.readFileSync(filename, 'utf-8').replace(/\r/g, ''); const inputHtml = settings.html ?? (filename ? readFile(filename) : null); const makePostRequest = () => { if (typeof inputHtml === 'string') { return request.post(settings.checkUrl).set('Content-Type', 'text/html; encoding=utf-8').send(inputHtml); } else { throw new Error('[w3c-html-validator] Invalid HTML input provided.'); } }; const makeGetRequest = () => request.get(settings.checkUrl).query({ doc: settings.website }); const w3cRequest = inputHtml ? makePostRequest() : makeGetRequest(); w3cRequest.set('User-Agent', 'W3C HTML Validator ~ github.com/center-key/w3c-html-validator'); w3cRequest.query({ out: settings.output }); const json = settings.output === 'json'; const success = '<p class="success">'; const titleLookup = { html: 'HTML String (characters: ' + (inputHtml?.length ?? 0) + ')', filename: filename, website: settings.website, }; const filterMessages = (response) => { const aboveInfo = (subType) => settings.ignoreLevel === 'info' && !!subType; const aboveIgnoreLevel = (message) => !settings.ignoreLevel || message.type !== 'info' || aboveInfo(message.subType); const matchesSkipPattern = (title) => (settings.ignoreMessages ?? []).some((pattern) => typeof title === 'string' && typeof pattern === 'string' ? title.includes(pattern) : pattern instanceof RegExp && typeof title === 'string' ? pattern.test(title) : false ); const isImportant = (message) => aboveIgnoreLevel(message) && !matchesSkipPattern(message.message); if (json) response.body.messages = response.body.messages?.filter(isImportant) ?? []; return response; }; const toValidatorResults = (response) => ({ validates: json ? !response.body.messages.length : !!response.text?.includes(success), mode: mode, title: titleLookup[mode] || '', html: inputHtml, filename: filename, website: settings.website || null, output: settings.output, status: response.statusCode || -1, messages: json ? response.body.messages : null, display: json ? null : response.text, }); const handleError = (reason) => { const response = reason.response; const getMsg = () => [response.status, response.res.statusMessage, response.request.url]; const message = response ? getMsg() : [reason.errno, reason.message]; const errRes = response ?? {}; errRes.body = { messages: [{ type: 'network-error', message: message.join(' ') }] }; return toValidatorResults(errRes); }; return w3cRequest.then(filterMessages).then(toValidatorResults).catch(handleError); }, summary(numFiles) { log(colors.gray('w3c-html-validator'), colors.magenta('files: ' + numFiles)); }, reporter(results, options) { const defaults = { continueOnFail: false, maxMessageLen: null, quiet: false, title: null, }; const settings = { ...defaults, ...options }; if (typeof results?.validates !== 'boolean') throw Error(colors.red.bold('[w3c-html-validator] Invalid results for reporter(): ' + String(results))); const messages = results.messages ?? []; const title = settings.title ?? results.title; const status = results.validates ? colors.green.bold('✔ pass') : colors.red.bold('✘ fail'); const count = results.validates ? '' : '(messages: ' + messages.length + ')'; if (!results.validates || !settings.quiet) log(colors.gray('w3c-html-validator'), status, colors.blue.bold(title), colors.white(count)); const typeColorMap = { error: colors.red.bold, warning: colors.yellow.bold, info: colors.green.bold, }; const logMessage = (message) => { const type = message.subType ?? message.type; const typeColor = typeColorMap[type] ?? colors.red.bold; const location = `line ${message.lastLine}, column ${message.firstColumn}:`; const lineText = message.extract?.replace(/\n/g, '\\n'); const maxLen = settings.maxMessageLen ?? undefined; log(typeColor('HTML ' + type + ':'), message.message.substring(0, maxLen)); if (message.lastLine) log(colors.white(location), colors.magenta(lineText)); }; messages.forEach(logMessage); const failDetails = () => { const toString = (message) => `${message.subType ?? message.type} line ${message.lastLine} column ${message.firstColumn}`; if (!results.filename) { return results.messages?.[0]?.message || 'Unknown error'; } else { return results.filename + ' -- ' + (results.messages?.map(toString).join(', ') || 'No messages'); } }; if (!settings.continueOnFail && !results.validates) throw Error(colors.red.bold('[w3c-html-validator] Failed: ' + failDetails())); return results; }, }; export { w3cHtmlValidator };