linkinator
Advanced tools
Comparing version 2.8.2 to 2.9.0
@@ -10,2 +10,3 @@ #!/usr/bin/env node | ||
const config_1 = require("./config"); | ||
const logger_1 = require("./logger"); | ||
// eslint-disable-next-line @typescript-eslint/no-var-requires | ||
@@ -16,2 +17,3 @@ const toCSV = util_1.promisify(require('jsonexport')); | ||
updateNotifier({ pkg }).notify(); | ||
/* eslint-disable no-process-exit */ | ||
const cli = meow(` | ||
@@ -47,10 +49,7 @@ Usage | ||
--server-root | ||
When scanning a locally directory, customize the location on disk | ||
When scanning a locally directory, customize the location on disk | ||
where the server is started. Defaults to the path passed in [LOCATION]. | ||
--silent | ||
Only output broken links | ||
--skip, -s | ||
List of urls in regexy form to not include in the check. | ||
List of urls in regexy form to not include in the check. | ||
@@ -60,2 +59,6 @@ --timeout | ||
--verbosity | ||
Override the default verbosity for this command. Available options are | ||
'debug', 'info', 'warning', 'error', and 'none'. Defaults to 'warning'. | ||
Examples | ||
@@ -78,2 +81,3 @@ $ linkinator docs/ | ||
serverRoot: { type: 'string' }, | ||
verbosity: { type: 'string' }, | ||
}, | ||
@@ -90,15 +94,8 @@ booleanDefault: undefined, | ||
const start = Date.now(); | ||
if (!flags.silent) { | ||
log(`🏊♂️ crawling ${cli.input}`); | ||
} | ||
const verbosity = parseVerbosity(cli.flags); | ||
const format = parseFormat(cli.flags); | ||
const logger = new logger_1.Logger(verbosity, format); | ||
logger.error(`🏊♂️ crawling ${cli.input}`); | ||
const checker = new index_1.LinkChecker(); | ||
// checker.on('pagestart', url => { | ||
// if (!flags.silent) { | ||
// log(`\n Scanning ${chalk.grey(url)}`); | ||
// } | ||
// }); | ||
checker.on('link', (link) => { | ||
if (flags.silent && link.state !== index_1.LinkState.BROKEN) { | ||
return; | ||
} | ||
let state = ''; | ||
@@ -108,11 +105,13 @@ switch (link.state) { | ||
state = `[${chalk.red(link.status.toString())}]`; | ||
logger.error(`${state} ${chalk.gray(link.url)}`); | ||
break; | ||
case index_1.LinkState.OK: | ||
state = `[${chalk.green(link.status.toString())}]`; | ||
logger.warn(`${state} ${chalk.gray(link.url)}`); | ||
break; | ||
case index_1.LinkState.SKIPPED: | ||
state = `[${chalk.grey('SKP')}]`; | ||
logger.info(`${state} ${chalk.gray(link.url)}`); | ||
break; | ||
} | ||
log(`${state} ${chalk.gray(link.url)}`); | ||
}); | ||
@@ -128,17 +127,10 @@ const opts = { | ||
if (flags.skip) { | ||
if (typeof flags.skip === 'string') { | ||
opts.linksToSkip = flags.skip.split(' ').filter(x => !!x); | ||
} | ||
else if (Array.isArray(flags.skip)) { | ||
opts.linksToSkip = flags.skip; | ||
} | ||
opts.linksToSkip = flags.skip.split(' ').filter(x => !!x); | ||
} | ||
const result = await checker.check(opts); | ||
log(); | ||
const format = flags.format ? flags.format.toLowerCase() : null; | ||
if (format === 'json') { | ||
if (format === logger_1.Format.JSON) { | ||
console.log(JSON.stringify(result, null, 2)); | ||
return; | ||
} | ||
else if (format === 'csv') { | ||
else if (format === logger_1.Format.CSV) { | ||
const csv = await toCSV(result.links); | ||
@@ -149,19 +141,47 @@ console.log(csv); | ||
else { | ||
// Build a collection scanned links, collated by the parent link used in | ||
// the scan. For example: | ||
// { | ||
// "./README.md": [ | ||
// { | ||
// url: "https://img.shields.io/npm/v/linkinator.svg", | ||
// status: 200 | ||
// .... | ||
// } | ||
// ], | ||
// } | ||
const parents = result.links.reduce((acc, curr) => { | ||
if (!flags.silent || curr.state === index_1.LinkState.BROKEN) { | ||
const parent = curr.parent || ''; | ||
if (!acc[parent]) { | ||
acc[parent] = []; | ||
} | ||
acc[parent].push(curr); | ||
const parent = curr.parent || ''; | ||
if (!acc[parent]) { | ||
acc[parent] = []; | ||
} | ||
acc[parent].push(curr); | ||
return acc; | ||
}, {}); | ||
Object.keys(parents).forEach(parent => { | ||
const links = parents[parent]; | ||
log(chalk.blue(parent)); | ||
// prune links based on verbosity | ||
const links = parents[parent].filter(link => { | ||
if (verbosity === logger_1.LogLevel.NONE) { | ||
return false; | ||
} | ||
if (link.state === index_1.LinkState.BROKEN) { | ||
return true; | ||
} | ||
if (link.state === index_1.LinkState.OK) { | ||
if (verbosity <= logger_1.LogLevel.WARNING) { | ||
return true; | ||
} | ||
} | ||
if (link.state === index_1.LinkState.SKIPPED) { | ||
if (verbosity <= logger_1.LogLevel.INFO) { | ||
return true; | ||
} | ||
} | ||
return false; | ||
}); | ||
if (links.length === 0) { | ||
return; | ||
} | ||
logger.error(chalk.blue(parent)); | ||
links.forEach(link => { | ||
if (flags.silent && link.state !== index_1.LinkState.BROKEN) { | ||
return; | ||
} | ||
let state = ''; | ||
@@ -171,11 +191,13 @@ switch (link.state) { | ||
state = `[${chalk.red(link.status.toString())}]`; | ||
logger.error(` ${state} ${chalk.gray(link.url)}`); | ||
break; | ||
case index_1.LinkState.OK: | ||
state = `[${chalk.green(link.status.toString())}]`; | ||
logger.warn(` ${state} ${chalk.gray(link.url)}`); | ||
break; | ||
case index_1.LinkState.SKIPPED: | ||
state = `[${chalk.grey('SKP')}]`; | ||
logger.info(` ${state} ${chalk.gray(link.url)}`); | ||
break; | ||
} | ||
log(` ${state} ${chalk.gray(link.url)}`); | ||
}); | ||
@@ -187,14 +209,36 @@ }); | ||
const borked = result.links.filter(x => x.state === index_1.LinkState.BROKEN); | ||
console.error(chalk.bold(`${chalk.red('ERROR')}: Detected ${borked.length} broken links. Scanned ${chalk.yellow(result.links.length.toString())} links in ${chalk.cyan(total.toString())} seconds.`)); | ||
// eslint-disable-next-line no-process-exit | ||
logger.error(chalk.bold(`${chalk.red('ERROR')}: Detected ${borked.length} broken links. Scanned ${chalk.yellow(result.links.length.toString())} links in ${chalk.cyan(total.toString())} seconds.`)); | ||
process.exit(1); | ||
} | ||
log(chalk.bold(`🤖 Successfully scanned ${chalk.green(result.links.length.toString())} links in ${chalk.cyan(total.toString())} seconds.`)); | ||
logger.error(chalk.bold(`🤖 Successfully scanned ${chalk.green(result.links.length.toString())} links in ${chalk.cyan(total.toString())} seconds.`)); | ||
} | ||
function log(message = '\n') { | ||
function parseVerbosity(flags) { | ||
if (flags.silent && flags.verbosity) { | ||
throw new Error('The SILENT and VERBOSITY flags cannot both be defined. Please consider using VERBOSITY only.'); | ||
} | ||
if (flags.silent) { | ||
return logger_1.LogLevel.ERROR; | ||
} | ||
if (!flags.verbosity) { | ||
return logger_1.LogLevel.WARNING; | ||
} | ||
const verbosity = flags.verbosity.toUpperCase(); | ||
const options = Object.values(logger_1.LogLevel); | ||
if (!options.includes(verbosity)) { | ||
throw new Error(`Invalid flag: VERBOSITY must be one of [${options.join(',')}]`); | ||
} | ||
return logger_1.LogLevel[verbosity]; | ||
} | ||
function parseFormat(flags) { | ||
if (!flags.format) { | ||
console.log(message); | ||
return logger_1.Format.TEXT; | ||
} | ||
flags.format = flags.format.toUpperCase(); | ||
const options = Object.values(logger_1.Format); | ||
if (!options.includes(flags.format)) { | ||
throw new Error("Invalid flag: FORMAT must be 'TEXT', 'JSON', or 'CSV'."); | ||
} | ||
return logger_1.Format[flags.format]; | ||
} | ||
main(); | ||
//# sourceMappingURL=cli.js.map |
{ | ||
"name": "linkinator", | ||
"description": "Find broken links, missing images, etc in your HTML. Scurry around your site and find all those broken links.", | ||
"version": "2.8.2", | ||
"version": "2.9.0", | ||
"license": "MIT", | ||
@@ -6,0 +6,0 @@ "repository": "JustinBeckwith/linkinator", |
@@ -67,5 +67,2 @@ # 🐿 linkinator | ||
--silent | ||
Only output broken links. | ||
--skip, -s | ||
@@ -76,2 +73,6 @@ List of urls in regexy form to not include in the check. | ||
Request timeout in ms. Defaults to 0 (no timeout). | ||
--verbosity | ||
Override the default verbosity for this command. Available options are | ||
'debug', 'info', 'warning', 'error', and 'none'. Defaults to 'warning'. | ||
``` | ||
@@ -78,0 +79,0 @@ |
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
69741
18
919
282