From 27c6139ca3f9aa46ace77bc5d367d25b8d627e78 Mon Sep 17 00:00:00 2001
From: Maximiliano Salvatti <40447063+msalvatti@users.noreply.github.com>
Date: Mon, 3 Aug 2026 19:38:13 -0300
Subject: [PATCH] fix(gate): collect README links from the prose, not from the
code fences
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
The published-surface gate verified every link in the README, including the ones
inside fenced code blocks. An `` in an example email template is
a string the example builds at runtime, not a link a reader can click, and the
checker tried to resolve the placeholder as a repository path — reporting
`${url} does not exist in the repository` and failing the build for a link that
does not exist.
Collection now starts from the prose, as the heading/anchor check in the same
file already did. The snippet extractor still reads the full README: the fences
are exactly what it compiles.
Found in @bymax-one/nest-auth, where a README example tripped it; applied here so
the gate does not fail this repository for the same non-reason the day its README
gains an HTML anchor inside an example.
---
scripts/check-published-surface.mjs | 10 +++++++---
1 file changed, 7 insertions(+), 3 deletions(-)
diff --git a/scripts/check-published-surface.mjs b/scripts/check-published-surface.mjs
index da8b463..49a285b 100644
--- a/scripts/check-published-surface.mjs
+++ b/scripts/check-published-surface.mjs
@@ -218,7 +218,11 @@ async function checkLinks() {
// a naive link regex captures the IMAGE — probing shields.io on every run and
// failing the build when it rate-limits, for a reason that is not ours. The
// image is stripped first so only the target remains.
- const clickable = README.replace(/!\[[^\]]*\]\([^)]*\)/g, '')
+ //
+ // Collection starts from the prose for the same reason the anchor set does: an
+ // `` inside a fence is a string an example builds at runtime,
+ // not a link a reader can click, and the checker cannot resolve a placeholder.
+ const clickable = README_PROSE.replace(/!\[[^\]]*\]\([^)]*\)/g, '')
const links = new Set([
...[...clickable.matchAll(/\[[^\]]*\]\((https?:\/\/[^)\s]+)\)/g)].map((m) => m[1]),
...[...clickable.matchAll(/ m[1]),
@@ -226,8 +230,8 @@ async function checkLinks() {
// Both spellings: the section links are Markdown, but the header navigation is
// raw HTML. Collecting only the first left this README's nav bar unchecked.
const internal = new Set([
- ...[...README.matchAll(/\[[^\]]*\]\((#[^)\s]+)\)/g)].map((m) => m[1].toLowerCase()),
- ...[...README.matchAll(/ m[1].toLowerCase()),
+ ...[...README_PROSE.matchAll(/\[[^\]]*\]\((#[^)\s]+)\)/g)].map((m) => m[1].toLowerCase()),
+ ...[...README_PROSE.matchAll(/ m[1].toLowerCase()),
])
// Relative links are the ones most likely to rot — a file gets renamed and