199 lines
5.5 KiB
JavaScript
199 lines
5.5 KiB
JavaScript
const fs = require("node:fs");
|
|
const path = require("node:path");
|
|
const yaml = require("js-yaml");
|
|
const { loadToolsConfig } = require("./config");
|
|
|
|
const DEFAULT_CACHE_DIR = "tools/cache";
|
|
const DEFAULT_CACHE_FILE = "external_links.yaml";
|
|
|
|
/**
|
|
* Resout le chemin du rapport des liens externes a partir de la configuration.
|
|
* @param {string} siteRoot Racine du projet.
|
|
* @returns {Promise<string>} Chemin absolu du rapport YAML.
|
|
*/
|
|
async function resolveExternalLinksReportPath(siteRoot) {
|
|
const rootDir = path.resolve(siteRoot);
|
|
const configPath = path.join(rootDir, "tools", "config", "config.json");
|
|
const config = await loadToolsConfig(configPath);
|
|
|
|
let cacheDir = DEFAULT_CACHE_DIR;
|
|
const externalLinks = config.externalLinks;
|
|
if (externalLinks && typeof externalLinks.cacheDir === "string" && externalLinks.cacheDir.trim()) {
|
|
cacheDir = externalLinks.cacheDir.trim();
|
|
}
|
|
|
|
let cacheFile = DEFAULT_CACHE_FILE;
|
|
if (externalLinks && typeof externalLinks.cacheFile === "string" && externalLinks.cacheFile.trim()) {
|
|
cacheFile = externalLinks.cacheFile.trim();
|
|
}
|
|
|
|
let resolvedCacheDir = cacheDir;
|
|
if (!path.isAbsolute(resolvedCacheDir)) {
|
|
resolvedCacheDir = path.join(rootDir, resolvedCacheDir);
|
|
}
|
|
|
|
if (path.isAbsolute(cacheFile)) {
|
|
return cacheFile;
|
|
}
|
|
|
|
return path.join(resolvedCacheDir, cacheFile);
|
|
}
|
|
|
|
/**
|
|
* Normalise la liste des emplacements associes a un lien.
|
|
* @param {unknown[]} rawLocations Emplacements bruts.
|
|
* @returns {Array<{ file: string, line: number|null, page: string|null }>}
|
|
*/
|
|
function normalizeLocations(rawLocations) {
|
|
if (!Array.isArray(rawLocations)) {
|
|
return [];
|
|
}
|
|
|
|
const locations = [];
|
|
for (const rawLocation of rawLocations) {
|
|
if (!rawLocation || typeof rawLocation !== "object") {
|
|
continue;
|
|
}
|
|
|
|
let file = null;
|
|
if (typeof rawLocation.file === "string" && rawLocation.file.trim()) {
|
|
file = rawLocation.file.trim();
|
|
}
|
|
if (!file) {
|
|
continue;
|
|
}
|
|
|
|
let line = null;
|
|
if (typeof rawLocation.line === "number" && Number.isFinite(rawLocation.line)) {
|
|
line = rawLocation.line;
|
|
}
|
|
|
|
let page = null;
|
|
if (typeof rawLocation.page === "string" && rawLocation.page.trim()) {
|
|
page = rawLocation.page.trim();
|
|
}
|
|
|
|
locations.push({ file, line, page });
|
|
}
|
|
|
|
return locations;
|
|
}
|
|
|
|
/**
|
|
* Normalise une entree du rapport.
|
|
* @param {unknown} rawLink Entree brute.
|
|
* @returns {{ url: string, status: number|null, locations: Array<{ file: string, line: number|null, page: string|null }> }|null}
|
|
*/
|
|
function normalizeLink(rawLink) {
|
|
if (!rawLink || typeof rawLink !== "object") {
|
|
return null;
|
|
}
|
|
if (typeof rawLink.url !== "string" || !rawLink.url.trim()) {
|
|
return null;
|
|
}
|
|
|
|
let status = null;
|
|
if (typeof rawLink.status === "number" && Number.isFinite(rawLink.status)) {
|
|
status = rawLink.status;
|
|
}
|
|
if (typeof rawLink.status === "string" && rawLink.status.trim()) {
|
|
const parsedStatus = Number.parseInt(rawLink.status, 10);
|
|
if (!Number.isNaN(parsedStatus)) {
|
|
status = parsedStatus;
|
|
}
|
|
}
|
|
|
|
return {
|
|
url: rawLink.url.trim(),
|
|
status,
|
|
locations: normalizeLocations(rawLink.locations),
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Reconstitue une liste de liens a partir de la section entries du cache.
|
|
* @param {Record<string, unknown>} entries Entrees brutes.
|
|
* @returns {Array<{ url: string, status: number|null, locations: Array<{ file: string, line: number|null, page: string|null }> }>}
|
|
*/
|
|
function buildLinksFromEntries(entries) {
|
|
const links = [];
|
|
for (const [url, rawEntry] of Object.entries(entries)) {
|
|
let status = null;
|
|
let locations = null;
|
|
if (rawEntry && typeof rawEntry === "object") {
|
|
status = rawEntry.status;
|
|
locations = rawEntry.locations;
|
|
}
|
|
const normalized = normalizeLink({
|
|
url,
|
|
status,
|
|
locations,
|
|
});
|
|
if (normalized) {
|
|
links.push(normalized);
|
|
}
|
|
}
|
|
return links;
|
|
}
|
|
|
|
/**
|
|
* Charge le rapport des liens externes.
|
|
* @param {string} reportPath Chemin absolu ou relatif du rapport YAML.
|
|
* @returns {{ generatedAt: string|null, links: Array<{ url: string, status: number|null, locations: Array<{ file: string, line: number|null, page: string|null }> }> }}
|
|
*/
|
|
function loadExternalLinksReport(reportPath) {
|
|
const resolvedPath = path.resolve(reportPath);
|
|
if (!fs.existsSync(resolvedPath)) {
|
|
return { generatedAt: null, links: [] };
|
|
}
|
|
|
|
const raw = yaml.load(fs.readFileSync(resolvedPath, "utf8")) || {};
|
|
let links = [];
|
|
if (Array.isArray(raw.links)) {
|
|
for (const rawLink of raw.links) {
|
|
const normalized = normalizeLink(rawLink);
|
|
if (normalized) {
|
|
links.push(normalized);
|
|
}
|
|
}
|
|
} else if (raw.entries && typeof raw.entries === "object") {
|
|
links = buildLinksFromEntries(raw.entries);
|
|
}
|
|
|
|
return {
|
|
generatedAt: raw.generatedAt || null,
|
|
links,
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Filtre les liens du rapport par code de statut HTTP.
|
|
* @param {{ links?: Array<{ status: number|null }> }} report Rapport charge.
|
|
* @param {number} statusCode Code a retenir.
|
|
* @returns {Array<{ url: string, status: number|null, locations: Array<{ file: string, line: number|null, page: string|null }> }>}
|
|
*/
|
|
function getLinksByStatus(report, statusCode) {
|
|
if (!report || !Array.isArray(report.links)) {
|
|
return [];
|
|
}
|
|
|
|
const links = [];
|
|
for (const link of report.links) {
|
|
if (!link || typeof link !== "object") {
|
|
continue;
|
|
}
|
|
if (link.status !== statusCode) {
|
|
continue;
|
|
}
|
|
links.push(link);
|
|
}
|
|
|
|
return links;
|
|
}
|
|
|
|
module.exports = {
|
|
resolveExternalLinksReportPath,
|
|
loadExternalLinksReport,
|
|
getLinksByStatus,
|
|
};
|