Download scripts/log-url-issues.js from AbdulElahGwaith/content: direct link, hf CLI and curl.
- Browser
- Download file 6.37 kB
-
https://huggingface.co/AbdulElahGwaith/content/resolve/main/scripts/log-url-issues.js
- Command line
-
hf download hf://AbdulElahGwaith/content/scripts/log-url-issues.js
-
curl -L -o log-url-issues.js https://huggingface.co/AbdulElahGwaith/content/resolve/main/scripts/log-url-issues.js
6.37 kB
| /* | |
| * The script logs locations of affected URLs due to following reasons: | |
| * - file deletion | |
| * - Markdown header updates | |
| */ | |
| import fs from "node:fs"; | |
| import { | |
| execGit, | |
| getRootDir, | |
| walkSync, | |
| getLocations, | |
| IMG_RX, | |
| stringToFragment, | |
| } from "./utils.js"; | |
| const rootDir = getRootDir(); | |
| const fileCache = new Map(); | |
| const anchorCache = new Map(); | |
| const deletedSlugs = []; | |
| const addedFragmentDetails = []; | |
| let deletedFragmentDetails = []; | |
| let isAllOk = true; | |
| function getFileContent(path) { | |
| if (fileCache.has(path)) { | |
| return fileCache.get(path); | |
| } | |
| const content = fs.readFileSync(path, "utf-8"); | |
| fileCache.set(path, content); | |
| return content; | |
| } | |
| function getFileAnchors(path) { | |
| if (anchorCache.has(path)) { | |
| return anchorCache.get(path); | |
| } | |
| const content = getFileContent(path); | |
| const headerAnchors = [...content.matchAll(/^#+ .*?$/gm)] | |
| .map((match) => match[0].toLowerCase()) | |
| .map((h) => h.replace(/#+ /g, "")) | |
| .map((h) => stringToFragment(h)); | |
| anchorCache.set(path, headerAnchors); | |
| return headerAnchors; | |
| } | |
| function getDeletedSlugs(fromStaging = true) { | |
| let result = ""; | |
| if (fromStaging) { | |
| // git status --short --porcelain | |
| result = execGit(["status", "--short", "--porcelain"], { cwd: "." }); | |
| if (result.trim()) { | |
| deletedSlugs.push( | |
| ...result | |
| .split("\n") | |
| .filter( | |
| (line) => | |
| /^\s*D\s+/gi.test(line) && | |
| line.includes("files/en-us") && | |
| (IMG_RX.test(line) || line.includes("index.md")), | |
| ) | |
| .map((line) => | |
| line.replaceAll(/^\s+|files\/en-us\/|\/index.md/gm, ""), | |
| ) | |
| .map((line) => line.split(/\s+/)[1]), | |
| ); | |
| } | |
| } else { | |
| // git diff --summary origin/main...HEAD | |
| result = execGit(["diff", "--summary", "origin/main...HEAD"], { cwd: "." }); | |
| if (result.trim()) { | |
| deletedSlugs.push( | |
| ...result | |
| .split("\n") | |
| .filter( | |
| (line) => | |
| line.includes("delete mode") && | |
| line.includes("files/en-us") && | |
| (IMG_RX.test(line) || line.includes("index.md")), | |
| ) | |
| .map((line) => line.replace(/^\s*delete mode \d+ /gm, "")) | |
| .map((line) => | |
| line.replaceAll(/^\s+|files\/en-us\/|\/index.md/gm, ""), | |
| ), | |
| ); | |
| } | |
| } | |
| } | |
| function getFragmentDetails(fromStaging = true) { | |
| let result = ""; | |
| if (fromStaging) { | |
| // get staged and unstaged changes | |
| result = execGit(["diff", "HEAD"], { cwd: "." }); | |
| } else { | |
| // get diff between branch base and HEAD | |
| result = execGit(["diff", "origin/main...HEAD"], { cwd: "." }); | |
| } | |
| if (result.trim()) { | |
| const segments = [ | |
| ...result.split("diff --git a/").filter((segment) => segment !== ""), | |
| ]; | |
| for (const segment of segments) { | |
| const path = segment | |
| .substring(0, segment.indexOf(" ")) | |
| .replaceAll(/files\/en-us\/|\/index.md/gm, ""); | |
| const addedHeaderRx = /^\+#+ .*$/gm; | |
| const addedFragments = [...segment.matchAll(addedHeaderRx)] | |
| .map((match) => match[0].toLowerCase()) | |
| .map((header) => header.replace(/\+#+ /g, "")) | |
| .map((header) => stringToFragment(header)); | |
| const removedHeaderRx = /^-#+ .*$/gm; | |
| [...segment.matchAll(removedHeaderRx)] | |
| .map((match) => match[0].toLowerCase()) | |
| .map((header) => header.replace(/-#+ /g, "")) | |
| .map((header) => stringToFragment(header)) | |
| .filter((header) => !addedFragments.includes(header)) | |
| .filter((header) => { | |
| // check if another header with same name exists in the file | |
| const absPath = `${rootDir}/files/en-us/${path}/index.md`; | |
| if (!fs.existsSync(absPath)) { | |
| return true; | |
| } | |
| const headerAnchors = getFileAnchors(absPath); | |
| return !headerAnchors.includes(header); | |
| }) | |
| .forEach((header) => { | |
| const fragment = `${path}#${header}`; | |
| if (!deletedFragmentDetails.includes(fragment)) { | |
| deletedFragmentDetails.push(fragment); | |
| } | |
| }); | |
| addedFragments.forEach((header) => | |
| addedFragmentDetails.push(`${path}#${header}`), | |
| ); | |
| } | |
| } | |
| } | |
| if (process.argv[2] !== "--workflow") { | |
| getDeletedSlugs(); | |
| getFragmentDetails(); | |
| } else { | |
| getDeletedSlugs(false); | |
| getFragmentDetails(false); | |
| } | |
| deletedFragmentDetails = deletedFragmentDetails.filter( | |
| (fragment) => !addedFragmentDetails.includes(fragment), | |
| ); | |
| if (deletedSlugs.length < 1 && deletedFragmentDetails.length < 1) { | |
| console.log("Nothing to check. 🎉"); | |
| process.exit(0); | |
| } | |
| console.log("deletedSlugs", deletedSlugs); | |
| console.log("deletedFragmentDetails", deletedFragmentDetails); | |
| for await (const filePath of walkSync(rootDir)) { | |
| if (filePath.endsWith("index.md")) { | |
| try { | |
| const content = | |
| fileCache.get(filePath) ?? fs.readFileSync(filePath, "utf-8"); | |
| const relativePath = filePath.substring(filePath.indexOf("files/en-us")); | |
| // check deleted links | |
| for (const slug of deletedSlugs) { | |
| const locations = getLocations( | |
| content, | |
| new RegExp(`/${slug}[)># \"']`, "mig"), | |
| ); | |
| if (locations.length) { | |
| isAllOk = false; | |
| for (const location of locations) { | |
| console.error( | |
| `ERROR:${relativePath}:${location.line}:${location.column}:Slug '${slug}' has been deleted`, | |
| ); | |
| } | |
| } | |
| } | |
| // check broken URL fragment | |
| for (const fragment of deletedFragmentDetails) { | |
| const locations = getLocations(content, `${fragment}(?: "|\\))`); | |
| // check fragments in the same file | |
| const urlParts = fragment.split("#"); | |
| if (filePath.includes(`${urlParts[0]}/index.md`)) { | |
| locations.push( | |
| ...getLocations(content, `\\(#${urlParts[1]}(?: "|\\))`), | |
| ); | |
| } | |
| if (locations.length) { | |
| isAllOk = false; | |
| for (const location of locations) { | |
| console.error( | |
| `ERROR:${relativePath}:${location.line}:${location.column}:URL fragment in URL '${fragment}' is broken`, | |
| ); | |
| } | |
| } | |
| } | |
| } catch (e) { | |
| console.error(`Error processing ${filePath}: ${e.message}`); | |
| throw e; | |
| } | |
| } | |
| } | |
| if (!isAllOk) { | |
| process.exit(1); | |
| } | |