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