Skip to content

fix(cli-utils): map cross-component doc URLs to qualified xrefs in urlToXref - #222

Closed
JakeSCahill wants to merge 4 commits into
mainfrom
fix/urltoxref-cross-component
Closed

fix(cli-utils): map cross-component doc URLs to qualified xrefs in urlToXref#222
JakeSCahill wants to merge 4 commits into
mainfrom
fix/urltoxref-cross-component

Conversation

@JakeSCahill

Copy link
Copy Markdown
Contributor

Bug

urlToXref in cli-utils/convert-doc-links.js converts docs.redpanda.com URLs found in operator godoc (used by doc-tools generate crd-docs and generate helm-docs) into Antora xrefs by unconditionally treating the first URL path segment as a module in the current component. For URLs that point at other Antora components on docs.redpanda.com, this produces broken xrefs.

Exact reproduction:

Input Before (broken) After
https://docs.redpanda.com/redpanda-connect/configuration/secrets/ xref:redpanda-connect:configuration/secrets.adoc xref:connect:configuration:secrets.adoc

The broken xref shipped in the generated modules/reference/pages/k-crd.adoc via redpanda-data/docs#1830 and was hand-fixed there (redpanda-data/docs#1830 (comment)). Every CRD regen reintroduces the breakage until the generator is fixed.

Fix

Add a verified URL-slug → Antora-component map. When a URL has no legacy /docs, /vX.Y, or /current prefix and its first path segment is in the map, emit a fully qualified xref:component:module:page.adoc resource ID with correct colon separation between module and page. A version segment directly after the slug (for example /streaming/current/...) is stripped. All other URLs keep the existing same-component behavior, and the map is deliberately not consulted after a legacy prefix is stripped (so /current/home/... still resolves to the current component's home module).

Shipped mapping (every entry verified against the target repo's antora.yml name: key; legacy slugs additionally verified via the live site's 301 redirects):

URL slug Component Verification
redpanda-connect (legacy), connect connect rp-connect-docs antora.yml; live 301 /redpanda-connect/.../connect/...
redpanda-cloud (legacy), cloud-data-platform cloud-data-platform cloud-docs antora.yml; live 301 /redpanda-cloud/.../cloud-data-platform/...
redpanda-labs (legacy), labs labs redpanda-labs docs/antora.yml; live 301 /redpanda-labs//labs/
streaming streaming redpanda-data/docs antora.yml; live 301 /current/.../streaming/current/...
agentic-data-plane agentic-data-plane adp-docs antora.yml
home, data-platform, self-managed same docs-site umbrella components' antora.yml files

Tests

New __tests__/cli-utils/convert-doc-links.test.js (18 cases), including:

  • the exact docs#1830 reproduction pair, with and without trailing slash
  • same-component /current/, /docs/, and /vX.Y/ URLs unchanged
  • mapped URL with a deeper path (/connect/components/inputs/kafka/xref:connect:components:inputs/kafka.adoc)
  • legacy cloud and labs slugs, versioned streaming URLs, module-only and component-only URLs
  • label preservation and the legacy-prefix/slug-map precedence rule

Full suite green: 34 suites, 768 tests passed. Also validated the converter against every docs.redpanda.com URL currently present in redpanda-data/redpanda-operator source: all legacy URLs unchanged, and the one cross-component URL now emits the same xref as the manual fix in docs#1830.

🤖 Generated with Claude Code

…lToXref

urlToXref treated the first path segment of every docs.redpanda.com URL
as a module in the current Antora component. Cross-component URLs such
as https://docs.redpanda.com/redpanda-connect/configuration/secrets/
produced the broken xref:redpanda-connect:configuration/secrets.adoc in
generated CRD and Helm docs.

Add a verified URL-slug-to-component map so cross-component URLs emit
fully qualified xref:component:module:page.adoc resource IDs, with
correct colon separation between module and page. Legacy /docs, /vX.Y,
and /current prefixed URLs keep the existing same-component behavior.

Prevents regression of the link hand-fixed in redpanda-data/docs#1830.
@netlify

netlify Bot commented Jul 28, 2026

Copy link
Copy Markdown

Deploy Preview for docs-extensions-and-macros ready!

Name Link
🔨 Latest commit 255c90c
🔍 Latest deploy log https://app.netlify.com/projects/docs-extensions-and-macros/deploys/6a70c6db9e8ffc00087522d7
😎 Deploy Preview https://deploy-preview-222--docs-extensions-and-macros.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.
🤖 Make changes Run an agent on this branch

To edit notification comments on pull requests, go to your Netlify project configuration.

@coderabbitai

coderabbitai Bot commented Jul 28, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Important

Review skipped

Auto incremental reviews are disabled on this repository.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: fc5ddab4-bf1a-4dfc-95e4-ba89b9f7aaa8

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

urlToXref now recognizes mapped documentation components and generates fully qualified Antora xrefs, including component and module index targets. It handles current, legacy, and versioned URL prefixes, normalizes paths, preserves bracketed labels, and retains historical behavior when legacy prefixes are stripped. A Jest suite covers invalid domains, same-component links, cross-component mappings, streaming URLs, nested paths, and index conversions.

Estimated code review effort: 3 (Moderate) | ~20 minutes

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main change: mapping cross-component doc URLs to qualified xrefs in urlToXref.
Description check ✅ Passed The description accurately explains the bug, fix, and tests, and it matches the changeset.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/urltoxref-cross-component

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (2)
__tests__/cli-utils/convert-doc-links.test.js (1)

56-120: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Solid cross-component coverage; a few map entries and the "beta" version segment go untested.

Traced every assertion here against the new logic — all pass as expected. Coverage is missing for VERSION_SEGMENT_RE's beta alternative and for the untested COMPONENT_SLUG_MAP entries (agentic-data-plane, home, data-platform, self-managed). Since a wrong map entry silently produces a broken xref (the exact class of bug this PR fixes), a couple more cheap assertions would close the gap.

✅ Suggested additional cases
+    it('strips a beta version segment after the slug', () => {
+      expect(
+        urlToXref('https://docs.redpanda.com/streaming/beta/manage/kubernetes/manage-resources/')
+      ).toBe('xref:streaming:manage:kubernetes/manage-resources.adoc');
+    });
+
+    it('maps the home umbrella component', () => {
+      expect(urlToXref('https://docs.redpanda.com/home/')).toBe('xref:home::index.adoc');
+    });
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@__tests__/cli-utils/convert-doc-links.test.js` around lines 56 - 120, Extend
the cross-component URL tests around urlToXref to cover VERSION_SEGMENT_RE’s
beta version form and add assertions for the unmapped COMPONENT_SLUG_MAP entries
agentic-data-plane, home, data-platform, and self-managed. Verify each URL
produces the expected component-qualified xref, preserving the existing test
style.
cli-utils/convert-doc-links.js (1)

84-112: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Correctness verified; consider extracting the cross-component branch for readability.

Manually traced this against every new test case (component-only, module-only, deep paths, streaming version-stripping, legacy-prefix precedence) — all outputs match expectations. This block is the fix for the docs#1830 class of bug, so keeping it easy to reason about pays off for future maintenance. Consider extracting the "resolve component" and "build cross-component xref" steps into small named helpers.

♻️ Proposed extraction
-  // Build module + path + .adoc
-  let xref;
-  const component = !hadLegacyPrefix && segments.length > 0
-    ? COMPONENT_SLUG_MAP[segments[0]]
-    : undefined;
-  if (segments.length === 0) {
-    xref = 'xref:index.adoc';
-  } else if (component) {
-    // Cross-component URL: emit a fully qualified resource ID.
-    segments.shift();
-    // Drop a version segment that may follow the slug (for example
-    // /streaming/current/manage/...)
-    if (segments.length > 0 && VERSION_SEGMENT_RE.test(segments[0])) {
-      segments.shift();
-    }
-    if (segments.length === 0) {
-      xref = `xref:${component}::index.adoc`;
-    } else {
-      const moduleName = segments.shift();
-      const fileName   = (segments.length > 0 ? segments.join('/') : 'index') + '.adoc';
-      xref = `xref:${component}:${moduleName}:${fileName}`;
-    }
+  // Build module + path + .adoc
+  let xref;
+  const component = !hadLegacyPrefix ? COMPONENT_SLUG_MAP[segments[0]] : undefined;
+  if (segments.length === 0) {
+    xref = 'xref:index.adoc';
+  } else if (component) {
+    xref = buildCrossComponentXref(component, segments);
   } else {
     const moduleName = segments.shift();
     const pagePath   = segments.join('/');
     const fileName   = (pagePath || moduleName) + '.adoc';
     xref = `xref:${moduleName}:${fileName}`;
   }
// Cross-component URL: emit a fully qualified resource ID, dropping the
// slug and an optional trailing version segment (e.g. /streaming/current/...).
function buildCrossComponentXref(component, segments) {
  segments.shift();
  if (segments.length > 0 && VERSION_SEGMENT_RE.test(segments[0])) {
    segments.shift();
  }
  if (segments.length === 0) {
    return `xref:${component}::index.adoc`;
  }
  const moduleName = segments.shift();
  const fileName   = (segments.length > 0 ? segments.join('/') : 'index') + '.adoc';
  return `xref:${component}:${moduleName}:${fileName}`;
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@cli-utils/convert-doc-links.js` around lines 84 - 112, Extract the
cross-component xref construction from the main conversion flow into a named
helper such as buildCrossComponentXref, preserving slug removal, optional
version-segment stripping, and component-only, module-only, and deep-path
outputs. Keep the existing hadLegacyPrefix/component resolution and ordinary
index handling in the surrounding logic, and call the helper only when a mapped
component is found.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@__tests__/cli-utils/convert-doc-links.test.js`:
- Around line 56-120: Extend the cross-component URL tests around urlToXref to
cover VERSION_SEGMENT_RE’s beta version form and add assertions for the unmapped
COMPONENT_SLUG_MAP entries agentic-data-plane, home, data-platform, and
self-managed. Verify each URL produces the expected component-qualified xref,
preserving the existing test style.

In `@cli-utils/convert-doc-links.js`:
- Around line 84-112: Extract the cross-component xref construction from the
main conversion flow into a named helper such as buildCrossComponentXref,
preserving slug removal, optional version-segment stripping, and component-only,
module-only, and deep-path outputs. Keep the existing hadLegacyPrefix/component
resolution and ordinary index handling in the surrounding logic, and call the
helper only when a mapped component is found.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 52e7a6e8-4315-473f-843e-667bb872b3dd

📥 Commits

Reviewing files that changed from the base of the PR and between ad02a1f and 86e3e9e.

📒 Files selected for processing (2)
  • __tests__/cli-utils/convert-doc-links.test.js
  • cli-utils/convert-doc-links.js

@JakeSCahill
JakeSCahill requested a review from a team July 30, 2026 07:43
Deep links like .../cluster-properties/#kafka_batch_max_bytes lost
their anchor: urlToXref built the xref from url.pathname only and
silently dropped url.hash, so converted links landed at the top of the
page instead of the referenced property or section. Found during
end-to-end testing against the real connect 4.103.0 data dump, which
carries fragment-bearing docs URLs.

The fragment is appended to the xref target before the label, matching
AsciiDoc's xref:module:page.adoc#fragment[label] form. Pre-existing on
main, fixed here because this branch already reworks urlToXref.
@JakeSCahill

Copy link
Copy Markdown
Contributor Author

Pushed a follow-up commit (499b073) fixing a defect found during the end-to-end test of this PR against the real connect 4.103.0 data: urlToXref silently dropped URL fragments, so a real dump URL like .../reference/properties/cluster-properties/#kafka_batch_max_bytes converted to an xref that lands at the top of the page instead of the property. The bug pre-dates this branch, but since this PR already reworks urlToXref, fixing it here avoids a guaranteed conflict with a separate PR.

Fragments now append to the xref target before the label (xref:reference:properties/cluster-properties.adoc#kafka_batch_max_bytes), with three new tests covering the legacy-prefix, cross-component-with-label, and no-fragment cases. Full suite 771/771 green.

…p entries

CodeRabbit review nitpick: VERSION_SEGMENT_RE's beta alternative and
the agentic-data-plane, home, data-platform, and self-managed map
entries had no assertions. A wrong map entry silently produces a
broken xref, which is the class of bug this branch fixes, so each
entry now has one.
@JakeSCahill

Copy link
Copy Markdown
Contributor Author

CodeRabbit's two nitpicks addressed in d6b446e: added assertions for the beta version segment and every previously untested slug-map entry (agentic-data-plane, home, data-platform, self-managed) — 24 tests on the module now, full suite 774 green. Skipped the suggested helper extraction: the cross-component branch is 25 lines with the reasoning in comments, and restructuring it mid-review after the fragment fix would add churn without changing behavior.

@micheleRP

Copy link
Copy Markdown
Contributor

Reviewed the logic against the actual antora.yml files in each target repo. The approach is right and the fragment-preservation commit is a good catch, but two of the slug-map entries would emit broken xrefs — the same failure class this PR fixes. Worth fixing before merge; nothing here needs a redesign.

Fix before merge

1. labs is not a component name.

// redpanda-labs (docs/antora.yml name: labs)
'redpanda-labs': 'labs', // legacy slug
'labs': 'labs',

The only antora.yml in redpanda-labs is docs/antora.yml, and its name is redpanda-labs, not labs:

$ find . -name antora.yml -not -path './node_modules/*' | while read f; do printf "%-24s %s\n" "$f" "$(grep -E '^name:' "$f")"; done
./docs/antora.yml        name: redpanda-labs

So https://docs.redpanda.com/redpanda-labs/docker-compose/single-broker/ produces xref:labs:docker-compose:single-broker.adoc, pointing at a component that doesn't exist. redpanda-labs is aggregated into the site (antora-playbook.yml:77), so this is a live broken link. The test at "maps the legacy redpanda-labs slug to the labs component" asserts the wrong value, so the suite currently locks the bug in.

Both slugs should map to redpanda-labs.

2. Component-only URLs assume a ROOT-module index.adoc that isn't always there.

/redpanda-connect/xref:connect::index.adoc, which Antora reads as "ROOT module of connect". But rp-connect-docs has start_page: home:index.adoc and its modules/ROOT/pages/ is empty — the landing page is modules/home/pages/index.adoc. So that xref doesn't resolve. cloud-data-platform has the same shape (empty modules/ROOT/pages/).

Of the mapped components, only home, self-managed, and data-platform actually have ROOT/pages/index.adoc. Resolving component-only URLs via the target's start_page would handle all of them.

Suggestion

The "first segment after the slug is a module" heuristic misfires on ROOT-module pages. docs-site sets html_extension_style: indexify (antora-playbook.yml:58), so every page URL ends in / and /data-platform/overview/ is genuinely ambiguous between "module overview, page index" and "ROOT page overview.adoc".

data-platform, self-managed, and home each have only a ROOT module, so for those the second reading is the correct one: xref:data-platform::overview.adoc, not the asserted xref:data-platform:overview:index.adoc. Same applies to the self-managed assertion. Either consult the content catalog, or restrict the module assumption to components known to be multi-module.

What works well

  • The urlToXref reproduction case from docs#1830 is fixed correctly, and connect, cloud-data-platform, streaming, and agentic-data-plane all check out against their antora.yml.
  • The fragment-preservation commit is a real find that stands on its own — dropped anchors are invisible because Antora doesn't validate fragments.
  • Adding a test per map entry is the right instinct, since a wrong entry fails silently. That's exactly what surfaced the labs issue.

Review finding: xref:<comp>::index.adoc assumes a ROOT index.adoc.
connect, cloud-data-platform, streaming, and agentic-data-plane have
none — their antora.yml start_page is home:index.adoc — so a
component-only URL emitted a broken xref. Those components now resolve
to their start page; labs and the docs-site umbrella components keep
::index.adoc, which is correct because they have a ROOT index page.

On the labs naming question: verified redpanda-labs' docs/antora.yml
says 'name: labs' on main (the branch the site playbook builds), and
live, /labs/ serves 200 while /redpanda-labs/ 301s to it, so the map
keeps labs as the component with redpanda-labs as the legacy slug.
@JakeSCahill

Copy link
Copy Markdown
Contributor Author

Both findings addressed:

  • Component-only URLs: confirmed — connect and cloud-data-platform (and streaming/agentic-data-plane) have no ROOT index.adoc; their start_page is home:index.adoc. Component-only URLs now resolve to the component's start page, and the tests assert the corrected values plus the ::index.adoc case for components that do have a ROOT index (labs, umbrella components).
  • labs naming: I verified rather than assumed — redpanda-labs' docs/antora.yml says name: labs on main, which is the branch the site playbook builds, and live /labs/ serves 200 while /redpanda-labs/ 301s to it. So the map keeps labs as the component with redpanda-labs as the legacy slug. If you saw a build where labs resolves as redpanda-labs, keen to see it, but three signals say labs is canonical.

@micheleRP micheleRP left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Solid diagnosis and the reproduction table makes the bug easy to confirm. I independently verified every entry in COMPONENT_SLUG_MAP against each repo's antora.yml on main — all correct:

Slug Component Verified
redpanda-connect, connect connect rp-connect-docs antora.yml
redpanda-cloud, cloud-data-platform cloud-data-platform cloud-docs antora.yml
redpanda-labs, labs labs redpanda-labs docs/antora.yml @ main
streaming streaming redpanda-data/docs antora.yml
agentic-data-plane agentic-data-plane adp-docs antora.yml

COMPONENT_START_PAGE checks out too — streaming, connect, cloud-data-platform, and agentic-data-plane all declare start_page: home:index.adoc, and labs plus the docs-site umbrellas have a real ROOT index.adoc. (My own local redpanda-labs checkout still says name: redpanda-labs; it's six months stale, so your labs reading is the right one.)

Requesting changes on one correctness issue that the tests currently lock in, plus one design question.

1. The three docs-site umbrella components are ROOT-only, so their mappings emit broken xrefs

docs-site/home, docs-site/self-managed, and docs-site/data-platform each contain only modules/ROOT/. The "first path segment after the slug is a module" rule doesn't hold for a single-module component:

URL This PR emits Correct
/home/how-to-use-these-docs/ xref:home:how-to-use-these-docs:index.adoc xref:home::how-to-use-these-docs.adoc
/self-managed/get-started/intro/ xref:self-managed:get-started:intro.adoc xref:self-managed::get-started/intro.adoc

/home/how-to-use-these-docs/ is a real published page (home/modules/ROOT/pages/how-to-use-these-docs.adoc), so this is reachable, not theoretical. The maps the docs-site umbrella components test asserts the broken forms, which means the bug ships with a green test blessing it.

Two ways out: add a ROOT-only set that emits xref:<component>::<path>.adoc for these three, or drop them from the map entirely. Dropping them isn't a regression — the pre-PR behavior was equally broken — but either way the test expectations need to change. labs, connect, cloud-data-platform, agentic-data-plane, and streaming are all genuinely multi-module (verified: labs has docker-compose, clients, kubernetes, ...; adp-docs has get-started, cli, connect, ...), so those entries are correct as written.

2. Self-qualifying streaming gives up version awareness

The converter's output lands in the streaming component, which is versioned (26.2 on main, plus v/* branches). A component-qualified resource ID with no version resolves to the component's latest version, so xref:streaming:manage:kubernetes/manage-resources.adoc emitted into a v/* branch during a CRD or Helm regen links across versions to latest.

Emitting a same-component xref for streaming URLs (xref:manage:kubernetes/manage-resources.adoc) keeps the current page's version and is strictly safer. You'd still want streaming in the map so the version segment gets stripped — just skip the component prefix when the mapped component is the one being generated into.

3. Fragment preservation is an undeclared behavior change (minor, scope)

The url.hash block changes output for every previously-anchored URL — they were silently dropped before. It's a good fix, but it isn't in the title or the "Fix" section, and it's the kind of change worth naming: anchors on the live site are rendered heading IDs, and they don't always survive as valid AsciiDoc anchors. 262abc6 in this repo was a fix for exactly that mismatch. An unresolvable anchor degrades to the top of the page rather than failing the build, so the risk is low — it just shouldn't arrive unannounced.

Nits

  • VERSION_SEGMENT_RE accepts current|beta|v?\d+\.\d+ but misses three-part versions (v25.1.2) and latest. Fine for the URL shapes in use; worth a comment saying so.
  • The map is a hardcoded snapshot of five repos' antora.yml. A comment pointing at the source of truth would help whoever hits the next component rename.
  • The test suite is genuinely thorough (26 cases) and the legacy-prefix precedence test is a nice touch — the hadLegacyPrefix guard is the subtle part and it's both correct and well documented.

Version bump needed

This changes cli-utils/convert-doc-links.js with no package.json bump, and npm is at 5.3.5. Per the repo's publish rule the fix won't reach consumers until a later PR bumps. #244 claims 5.3.6 and #245 claims 5.3.7, so this needs a version assigned before merge.

@micheleRP
micheleRP dismissed their stale review August 5, 2026 20:27

Downgrading this to non-blocking. The findings in the review above still stand — I'm just not gating the PR on them, so you can land on your own judgement.

@JakeSCahill

Copy link
Copy Markdown
Contributor Author

Closing in favor of #247, which implements the redesign suggested here: the conversion now runs inside the Antora runtime as a contentClassified extension that reverses Antora's own xref→URL mapping against the live content catalog, so there's no slug map, no module-vs-ROOT-page guessing, and no index.adoc/start_page edge cases — a URL converts only if it matches a page actually published in the build.

Two things from this thread carried over directly: the fragment-preservation behavior and the docs#1830 reproduction case (now asserted both in unit tests and in an end-to-end Antora build). The dynamic-playbook question dissolved entirely — running in the real build means all content sources are already there.

Thanks @micheleRP for the review findings that motivated the pivot; the ROOT-only umbrella components and start_page cases were exactly the class of guesswork the catalog-backed approach eliminates.

@JakeSCahill JakeSCahill closed this Aug 6, 2026
JakeSCahill added a commit that referenced this pull request Aug 6, 2026
… time

Successor to #222. Instead of guessing URL structure with a hardcoded
slug map, run the conversion inside the Antora runtime where the content
catalog is the source of truth:

- extensions/url-to-xref.js rewrites docs.redpanda.com URLs in page and
  partial content to xrefs at contentClassified, but only when the URL
  maps to a page published in the current build. Unmapped internal URLs
  stay raw and are logged as warnings, making the extension a
  broken-internal-link detector. Legacy URL shapes (/docs/, /current/,
  /vX.Y/, old component slugs) resolve through verified candidate
  rewrites; fragments and labels are preserved; latest-version targets
  emit unversioned xrefs, older targets emit version@ xrefs.
- extensions/external-link-checker.js verifies every external URL
  responds (HEAD with GET fallback, one retry, concurrency-capped) and
  reports dead links in the build log; fail_on_broken escalates to
  error level for CI.
- extensions/util/scan-content-urls.js is the shared scanner; it skips
  listing/literal/fenced/passthrough blocks, inline code spans, and
  attribute entries.
- doc-tools generate crd-spec/helm-spec no longer convert URLs at
  generation time (cli-utils/convert-doc-links.js removed): raw docs
  URLs are valid links everywhere, and the build now upgrades them,
  which fixes the broken cross-component xrefs from docs#1830 without
  a maintained slug map.
- Tests: 54 unit tests over the scanner and both extensions, plus an
  end-to-end suite that runs a real Antora build over a fixture site
  (local git sources, minimal UI bundle, local HTTP server for external
  links) and asserts on the published HTML and structured build log.

Co-Authored-By: Claude Fable 5 <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants