131 lines
4.0 KiB
JavaScript

const path = require('path');
const { SiteChecker } = require('broken-link-checker'); // eslint-disable-line import/no-unresolved
const { Renderer } = require('xlsx-renderer');
const { logger, spawnProcess } = require('./utils');
const ACCEPTABLE_STATUS_CODES = [undefined, 200, 429];
const PORT = 8088;
// start server
spawnProcess(`http-server ${path.resolve('.vuepress', 'dist')} -s -p ${PORT}`);
const stats = {
brokenInternal: 0,
brokenExternal: 0,
external: 0,
internal: 0,
_set: new Set()
};
const links = [];
const saveReport = async() => {
const result = await new Renderer().renderFromFile(path.resolve(__dirname, './check-links-report-template.xlsx'), {
stats: {
brokenInternal: stats.brokenInternal,
brokenExternal: stats.brokenExternal,
external: stats.external,
internal: stats.internal,
},
links,
at: new Date().toLocaleString()
});
await result.xlsx.writeFile('./report-check-links.xlsx');
logger.info(`XLSX report saved into ${path.resolve('./report-check-links.xlsx')}`);
};
const siteChecker = new SiteChecker(
{
excludeInternalLinks: false,
excludeExternalLinks: true,
excludeLinksToSamePage: true,
filterLevel: 2,
acceptedSchemes: ['http', 'https'],
excludedKeywords: [
'linkedin.com', // it always throws an error even if link really works
'github.com',
'https://jsfiddle.net/api/post/library/pure/', // from "edit in jsfiddle" button
'*/docs/*.*.*' // the old documentation
]
},
{
error: (error) => {
logger.error(error);
},
link: (result) => {
const isUnique = !stats._set.has(result.url.resolved);
stats._set.add(result.url.resolved);
const status = result.http.response?.statusCode;
if (result.broken) {
if (result.http.response && !ACCEPTABLE_STATUS_CODES.includes(status)) {
if (result.internal) {
// eslint-disable-next-line max-len
logger.error(`on: ${result.base.resolved} \t broken internal link ${status} => ${result.url.resolved} \t ${result.url.original} ;`);
stats.brokenInternal += isUnique;
} else {
logger.warn(`on: ${result.base.resolved} \t broken external link ${status} => ${result.url.resolved};`);
stats.brokenExternal += isUnique;
}
}
} else if (result.internal) {
stats.internal += isUnique;
} else {
logger.log(`on: ${result.base.resolved} \t external link ${status} => ${result.url.resolved};`);
stats.external += isUnique;
}
links.push({
status,
isBroken: result.broken,
isInternal: result.internal,
linkFrom: result.base.resolved,
linkTo: result.url.resolved,
linkToOriginal: result.url.original
});
},
end: async() => {
try {
await saveReport();
} catch (err) {
logger.warn('Error thrown while report was being generated', err);
}
logger.info('\nCHECK FOR BROKEN LINKS FINISHED');
logger.info(`Checked internals : ${stats.internal + stats.brokenInternal}`);
logger.info(`Checked externals : ${stats.external + stats.brokenExternal}`);
if (stats.brokenInternal || stats.brokenExternal) {
logger.error(`Broken internals: ${stats.brokenInternal}`);
logger.error(`Broken external: ${stats.brokenExternal}`);
if (stats.brokenInternal) {
logger.error('\nINTERNAL BROKEN LINKS DETECTED!');
process.exit(1);
} else {
logger.warn('\nEXTERNAL BROKEN LINKS DETECTED!');
process.exit(0);
}
}
logger.success('\nNO BROKEN LINKS DETECTED!');
process.exit(0);
}
}
);
const ARGUMENT_URL_DEFAULT = `http://127.0.0.1:${PORT}/docs/api`;
let [urlArg] = process.argv.slice(2);
urlArg = urlArg || ARGUMENT_URL_DEFAULT;
// run siteChecker
// timeout is needed because siteChecker would open URL before server started
setTimeout(() => {
logger.success('CHECK FOR BROKEN LINKS STARTED');
siteChecker.enqueue(urlArg);
}, 3000);