Select Git revision
pages_not_in_nav.cjs 4.88 KiB
#!/usr/bin/env node
/**
* @file pages_not_in_nav.js
* Generates a report of pages which are not included in navigation.yaml.
*/
const fs = require("fs");
const path = require("path");
const glob = require("glob");
const yaml = require("js-yaml");
// Configuration
const CONFIG = {
excludePatterns: [
/^architecture\//,
/^legal\//,
/^drawers\//,
/^operator\/adr\//,
/^charts\/development\//,
/^development\//,
/^omnibus\/development\//,
/^user\/application_security\/dast\/browser\/checks\/.+/,
/^user\/application_security\/api_security_testing\/checks\/.+/,
],
skipReasons: [
{ condition: "isRedirect", message: "redirected" },
{ condition: "isDeprecated", message: "deprecated" },
{ condition: "isIgnored", message: "ignored" },
],
colors: {
info: "\x1b[32m",
warn: "\x1b[33m",
error: "\x1b[31m",
reset: "\x1b[0m",
italics: "\u001b[3m",
},
};
// Utility functions
const utils = {
shouldExclude: (eachPath) =>
CONFIG.excludePatterns.some((pattern) => pattern.test(eachPath)),
getRelativePath: (filename, source, productConfig) => {
const absoluteProjectRoot = path.resolve(process.cwd());
const absoluteCloneDir = path.resolve(
absoluteProjectRoot,
productConfig.products[source].clone_dir,
);
const absoluteDocsDir = path.join(
absoluteCloneDir,
productConfig.products[source].docs_dir,
);
return path.relative(absoluteDocsDir, filename);
},
getPageData: (filename) => {
const safeFilename = path.resolve(filename);
if (!fs.existsSync(safeFilename)) {
throw new Error("File does not exist");
}
const contents = fs.readFileSync(safeFilename, "utf-8");
const title = contents
.split("\n")
.filter((line) => line.startsWith("title: "))
.toString()
.toLowerCase();
return {
filename: safeFilename,
isRedirect: contents.includes("redirect_to"),
isDeprecated:
title.includes("(deprecated)") || title.includes("(removed)"),
isIgnored: contents.includes("ignore_in_report"),
};
},
log: (type, message) =>
// eslint-disable-next-line no-console
console[type](
`${CONFIG.colors[type]}${type.toUpperCase()}:${CONFIG.colors.reset} ${message}`,
),
};
// Load files and output the report
async function main() {
const projectRoot = process.cwd();
const PRODUCTS_YAML_PATH = path.join(projectRoot, "data", "products.yaml");
const NAVIGATION_YAML_PATH = path.join(
projectRoot,
"data",
"navigation.yaml",
);
const productConfig = yaml.load(fs.readFileSync(PRODUCTS_YAML_PATH, "utf8"), {
schema: yaml.FAILSAFE_SCHEMA,
});
const navYaml = yaml.load(fs.readFileSync(NAVIGATION_YAML_PATH, "utf8"), {
schema: yaml.FAILSAFE_SCHEMA,
});
const nav = JSON.stringify(navYaml);
for (const source in productConfig.products) {
// Filter unwanted properties from the prototype chain (Eslint)
if (productConfig.products[source]) {
const product = productConfig.products[source];
const absoluteProjectRoot = path.resolve(process.cwd());
const absoluteCloneDir = path.resolve(
absoluteProjectRoot,
product.clone_dir,
);
const absoluteDocsDir = path.join(absoluteCloneDir, product.docs_dir);
const globPattern = path.join(absoluteDocsDir, "**", "*.md");
glob.sync(globPattern).forEach((filename) => {
try {
const pageData = utils.getPageData(filename);
// Skip the page if it meets a specified skip reason
for (const { condition, message } of CONFIG.skipReasons) {
if (pageData[condition]) {
if (process.env.VERBOSE) {
utils.log("info", `skipping ${message} page: ${filename}.`);
}
return;
}
}
// Convert the markdown filepath into a string that matches the URL path on the website.
let relativePath = utils
.getRelativePath(filename, source, productConfig)
.replace("_index.md", "")
.replace(".md", "/");
// Non-GitLab projects include their project name in the path
if (source !== "gitlab") {
relativePath = `${source}/${relativePath}`;
}
if (
!nav.includes(relativePath) &&
!utils.shouldExclude(relativePath)
) {
utils.log(
"warn",
`page at ${CONFIG.colors.italics}https://docs.gitlab.com/${relativePath}${CONFIG.colors.reset} is missing from global navigation!`,
);
}
} catch (error) {
utils.log(
"error",
`skipping '${filename}' because of error: '${error}'\nFix '${filename}' and try again.`,
);
}
});
}
}
}
// Run the script
main().catch((error) => {
utils.log("error", `An unexpected error occurred: ${error}`);
process.exit(1);
});