[go: nahoru, domu]

blob: a2d9bbfe0735315fb69fe1c526f52d7004bab55a [file] [log] [blame]
// Copyright 2020 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
'use strict';
const fs = require('fs');
const https = require('https');
const path = require('path');
const ts = require('typescript');
const readDirAsync = fs.promises.readdir;
const readFileAsync = fs.promises.readFile;
const ORIGIN_PATTERNS_TO_CHECK = [
new RegExp('^https://web.dev'),
new RegExp('^https://developers.google.com'),
new RegExp('^https://developer[s]?.chrome.com'),
];
const DIRECTORIES_TO_CHECK = [
'front_end',
];
const EXCLUDE_DIRECTORIES = [
'front_end/third_party',
];
const REQUEST_TIMEOUT = 5000;
const REDIRECTS_CONSIDERED_ERROR = new Set([
/* Multiple Choices */ 300,
/* Moved permanently */ 301,
/* Permament redirect */ 308,
]);
const ROOT_REPOSITORY_PATH = path.resolve(__dirname, '..');
const DIRECTORIES_TO_CHECK_PATHS = DIRECTORIES_TO_CHECK.map(directory => path.resolve(ROOT_REPOSITORY_PATH, directory));
async function findAllSourceFiles(directory) {
if (EXCLUDE_DIRECTORIES.includes(path.relative(ROOT_REPOSITORY_PATH, directory))) {
return [];
}
const dirEntries = await readDirAsync(directory, {withFileTypes: true});
const files = await Promise.all(dirEntries.map(dirEntry => {
const resolvedPath = path.resolve(directory, dirEntry.name);
if (dirEntry.isDirectory()) {
return findAllSourceFiles(resolvedPath);
}
if (dirEntry.isFile() && /\.(js|ts)$/.test(dirEntry.name)) {
return resolvedPath;
}
return []; // Let Array#flat filter out files we are not interested in.
}));
return files.flat();
}
function collectUrlsToCheck(node) {
const nodesToVisit = [node];
const urlsToCheck = [];
while (nodesToVisit.length) {
const currentNode = nodesToVisit.shift();
if (currentNode.kind === ts.SyntaxKind.StringLiteral ||
currentNode.kind === ts.SyntaxKind.NoSubstitutionTemplateLiteral) {
const checkUrl = ORIGIN_PATTERNS_TO_CHECK.some(originPattern => originPattern.test(currentNode.text));
if (checkUrl) {
urlsToCheck.push(currentNode.text);
}
}
nodesToVisit.push(...currentNode.getChildren());
}
return urlsToCheck;
}
async function collectUrlsToCheckFromFile(filePath) {
const content = await readFileAsync(filePath, 'utf8');
const sourceFile = ts.createSourceFile(filePath, content, ts.ScriptTarget.ESNext, true);
return collectUrlsToCheck(sourceFile);
}
async function checkUrls(urls) {
// clang-format off
const requestPromises = urls.map(url => new Promise(resolve => {
const request = https.request(url, {method: 'HEAD'}, response => {
resolve({url, statusCode: response.statusCode});
});
request.on('error', err => {
resolve({url, error: err});
});
request.setTimeout(REQUEST_TIMEOUT, _ => {
resolve({url, error: `Timed out after ${REQUEST_TIMEOUT}`});
});
request.end();
}));
// clang-format on
return Promise.all(requestPromises);
}
function includeRequestResultInOutput(requestResult) {
return requestResult.error || requestResult.statusCode !== 200;
}
function isErrorStatusCode(statusCode) {
return statusCode >= 400 || REDIRECTS_CONSIDERED_ERROR.has(statusCode);
}
function requestResultIsErronous(requestResult) {
return requestResult.error || isErrorStatusCode(requestResult.statusCode);
}
function printSelectedRequestResults(requestResults) {
const requestsToPrint = requestResults.filter(includeRequestResultInOutput);
if (requestsToPrint.length === 0) {
console.log('\nAll Urls are accessible and point to existing resources.\n');
return;
}
for (const requestResult of requestsToPrint) {
if (requestResult.error) {
console.error(`[Failure] ${requestResult.error} - ${requestResult.url}`);
} else if (isErrorStatusCode(requestResult.statusCode)) {
console.error(`[Failure] Status Code: ${requestResult.statusCode} - ${requestResult.url}`);
} else {
console.log(`Status Code: ${requestResult.statusCode} - ${requestResult.url}`);
}
}
}
async function main() {
process.stdout.write('Collecting JS/TS source files ... ');
const sourceFiles = (await Promise.all(DIRECTORIES_TO_CHECK_PATHS.map(findAllSourceFiles))).flat();
process.stdout.write(`${sourceFiles.length} files found.\n`);
process.stdout.write('Collecting Urls from files ... ');
const urlsToCheck = (await Promise.all(sourceFiles.map(collectUrlsToCheckFromFile))).flat();
const deduplicatedUrlsToCheck = new Set(urlsToCheck);
process.stdout.write(`${deduplicatedUrlsToCheck.size} unique Urls found.\n`);
process.stdout.write('Sending a HEAD request to each one ...\n');
const requestResults = await checkUrls([...deduplicatedUrlsToCheck]);
printSelectedRequestResults(requestResults);
const exitCode = requestResults.some(requestResultIsErronous) ? 1 : 0;
process.exit(exitCode);
}
main();