Skip to content

[#36] [#37] CI: check link anchors, head meta tags and the robots.txt Sitemap line - #38

Merged
vharseko merged 9 commits into
OpenIdentityPlatform:masterfrom
vharseko:issue-36-37-ci-anchors-meta
Oct 2, 2026
Merged

vharseko merged 9 commits into
OpenIdentityPlatform:masterfrom
vharseko:issue-36-37-ci-anchors-meta

Conversation

@vharseko

@vharseko vharseko commented Oct 1, 2026 •

Copy link
Copy Markdown
Member

Fixes #36
Fixes #37

Stacked on #28. Both issues extend the Build workflow and the baseline check that #28 adds, so this branch carries the commits of #28 (its current head, a2c1a17). Only the commits after it are this change; the branch will be rebased onto master once #28 is merged.

#36: anchors of links

  • build.yml: lychee runs with --include-fragments.
  • .github/build-baseline/broken-links.jq (new): the broken links of the lychee report as page <TAB> link, used by both check.sh and the job summary. It drops a Cannot find fragment whose anchor is the name of the target page itself (chap-resource-conf#chap-resource-conf): Antora renders the page title without an id, so the browser opens the top of the page, which is where the link points anyway. lychee's regular expressions have no back references, so this is done on the JSON report, as proposed in CI: check the anchors of links in the pull request build #36.
  • The job summary lists and counts each page/link pair once, as check.sh does, and shows how many links were left out: N checked, M broken (K more point to the top of their target page). (K counts occurrences, as lychee's errors does.)
  • Self-test: with empty known lists, check.sh must pass a link to the top of its target page, and must exit 1 with a New broken links annotation on a broken anchor and on a missing page linked at its own id (d/b.html#b as Cannot find file).
  • broken-links.txt: 35 broken anchors are added with check.sh --update: the 37 listed in CI: check the anchors of links in the pull request build #36 but the two .:chap-jee-agent-config.adoc ones, which [#1154] Replace legacy ForgeRock relative links in the guides with Antora xrefs OpenAM#1158 has fixed on master. The 10 known links of [#25] CI: build pull requests and make the site build reproducible #28 are unchanged. Reporting them in the product repositories is not done yet, it follows separately.

#37: head meta tags and the robots.txt Sitemap line

.github/build-baseline/head-meta.sh <site> (new), run as a new last step, also when the comparison with the known problems fails, so that one run reports both. It fails when:

  • robots.txt has no line Sitemap: <site.url>/sitemap.xml, site.url read from antora-playbook.yml, or sitemap.xml is missing;
  • a page carries other than exactly one each of <meta name="description", og:title, og:description, og:image, twitter:card, og:url; og:url is not required on 404.html. Redirect pages (<meta http-equiv="refresh", as Antora writes them) and the API docs copied by npm run copyApiDocs (the same paths as the link check excludes) are skipped.

Problems go to the job summary and as error annotations. A self-test step feeds the script a test site with an empty robots.txt, no sitemap.xml, a redirect page and a page without og:image, with twitter:card twice, that quotes a refresh tag, and requires exactly those four problems.

Verification

On a local build of this branch, with the commands of the workflow:

  • lychee with --include-fragments: 382 distinct broken anchors, of which 347 to the page itself and the 35 above; check.sh: 0 new, 0 no longer found; the job summary: 45 broken, 45 rows;
  • both self-test steps pass, and fail against each of 12 mutants: in broken-links.jq the status guard replaced by true, no filter, every fragment error dropped, the whole path compared, no rtrimstr; check.sh exiting on the new-entry path before the annotation; in head-meta.sh the tag count check deleted, -eq 1 → -ge 0, a loose http-equiv="refresh" match, the robots.txt or the sitemap.xml check deleted, always exit 0;
  • head-meta.sh build/site: 285 pages checked, 0 problems.

@vharseko
vharseko requested a review from maximthomas October 1, 2026 15:39
@vharseko vharseko added ci Continuous integration, build and publish workflows documentation Improvements or additions to documentation enhancement New feature or request labels Oct 1, 2026

@maximthomas maximthomas 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.

praise: the anchor filter is defined once and the gate is proven against real lychee output.

  • top_of_page lives only in .github/build-baseline/broken-links.jq:22-24, and check.sh:41 consumes it, so the gate and the summary share one predicate.
  • The Build run at 842554e is green with no ::error or ::notice lines, so the filter matches lychee 0.24.2's real JSON (Cannot find fragment, file:///site/ URLs) and both baselines reproduce exactly.
  • The head meta step runs under !cancelled() && steps.site.outcome == 'success' (build.yml:138), so one run reports both checks.

issue (non-blocking): the job summary counts and lists every occurrence of a broken link, while check.sh compares distinct page/link pairs.

.github/workflows/build.yml:97-106, .github/build-baseline/check.sh:41

lychee 0.24.2 does not deduplicate requests and never caches file URIs, and each error_map entry carries its own span, so a page that links the same broken anchor twice yields two lines. chap-jee-agents-features.html → index.html#j2ee-agent-general-properties (two hrefs, in the baseline) is printed twice and counted as 2 in the summary, and once by check.sh (sort -u). That contradicts "so the two cannot disagree"; pass/fail is unaffected. .errors counts occurrences too, so K must keep using the undeduplicated count.

        all=$(jq -r -f .github/build-baseline/broken-links.jq build/lychee.json)
        broken=$(printf '%s\n' "$all" | LC_ALL=C sort -u)
        {
          echo "## Links"
          echo
          jq -r --argjson n "$(printf '%s' "$broken" | grep -c . || true)" \
            --argjson all "$(printf '%s' "$all" | grep -c . || true)" \
            '"\(.total) checked, \($n) broken (\(.errors - $all) more point to the top of their target page)."' build/lychee.json

suggestion (non-blocking): no self-test case pins the Cannot find fragment status guard of top_of_page.

.github/workflows/build.yml:120-133, .github/build-baseline/broken-links.jq:23

Both fragment fixtures carry status Cannot find fragment. Replace broken-links.jq:23 with true and the self-test still exits 1/0/1 as at HEAD. The Compare step stays green as well: check.sh fails only on new entries (:72) and reports vanished ones as ::notice (:77). A new link to a missing page shaped dir/foo.html#foo would then be dropped silently, as would the 10 such entries already in broken-links.txt. The PR's two named mutants are killed; this third one is not.

        echo '{"error_map":{"/site/a.html":[{"url":"file:///site/d/b.html#b","status":{"text":"Cannot find file"}}]}}' \
          > "$t/build/lychee.json"
        if GITHUB_STEP_SUMMARY=/dev/null "$t/.github/build-baseline/check.sh" > /dev/null; then
          echo "check.sh passed a missing page linked at its own id" >&2
          exit 1
        fi

Pin: add this case after case 3. It turns red against the guard-less mutant. Changing case 2's URL to file:///site/d/b.html#b also exercises split("/") | last and rtrimstr(".html").


suggestion (non-blocking): self-test cases 1 and 3 accept any non-zero exit of check.sh, not the reported new entry.

.github/workflows/build.yml:116, :130

stdout is discarded, and neither exit code 1 (check.sh:94) nor the ::error title=New … line (check.sh:70) is checked. A mutant that crashes on the new-entry path (a broken escape at check.sh:69 under set -e) exits 3 with no annotation and passes both cases (measured: rc 3/0/3, 0 ::error title=New lines). Case 2 never reaches that path, and the real Compare step has no new entries.

        out=$(GITHUB_STEP_SUMMARY=/dev/null "$t/.github/build-baseline/check.sh") && rc=0 || rc=$?
        if [ "$rc" -ne 1 ] || ! grep -q '^::error title=New .*b#c' <<< "$out"; then
          echo "check.sh did not report the broken anchor" >&2
          exit 1
        fi

Pin: case 3 as above, and case 1 the same with its own message.


suggestion (non-blocking): the head meta / robots.txt step has no CI self-test; only the real site feeds it.

.github/workflows/build.yml:136-177

On a correct site, any mutant that hides problems stays green, e.g. deleting :159 or -eq 1 → -ge 0. :164 catches only an empty page set. The failing direction (571 problems) was shown by a local run, not in CI.

Pin: move build.yml:140-177 into a script that takes the site directory (e.g. .github/build-baseline/head-meta.sh). In the self-test, feed it a one-page fixture site with a matching robots.txt, an empty sitemap.xml and a page that lacks og:image and has twitter:card twice, and expect a non-zero exit.


suggestion (non-blocking): the redirect-page skip matches http-equiv="refresh" anywhere in the file, not only the redirect <meta>.

.github/workflows/build.yml:153

Asciidoctor escapes < but not ", so a content page that quotes a refresh tag (inline or in a listing) is skipped, is left out of pages, and goes unchecked for all six tags. No page does today, so this is latent. Antora's redirect page writes <meta http-equiv="refresh" content="0; url=…"> (@antora/redirect-producer/lib/produce-redirects.js:150), and escaped body text cannot contain that.

          grep -qF '<meta http-equiv="refresh"' "$f" && continue

- publish.yml: install with npm ci so that package-lock.json is honoured
- antora-playbook.yml: use the Antora default UI bundle kept in
  ui/ui-bundle.zip instead of the GitLab HEAD snapshot
- build.yml: build the site for every pull request and report Antora
  errors/warnings and broken links (lychee --offline) in the job
  summary; not enforced yet because of known content errors

Refs OpenIdentityPlatform#25
Node.js 20 actions are deprecated and are forced onto Node 24 with a
warning. Use the current majors: checkout v7, setup-node v7,
configure-pages v6, upload-pages-artifact v5, deploy-pages v5.

Refs OpenIdentityPlatform#25
The known ones, which come from the product repositories, are listed in
.github/build-baseline; check.sh fails on any other and reports the
known ones that are gone. check.sh --update rewrites the lists.

Refs OpenIdentityPlatform#25
- check.sh: fail on a missing or malformed build/antora.log or
  build/lychee.json instead of reading it as an empty build (and
  emptying the lists with --update)
- check.sh: keep repeated Antora errors, so that a second occurrence
  of a known one fails the check
- check.sh, build.yml: escape %, CR and LF in annotation text
- build.yml: remove build/antora.log before the build, Antora appends
- build.yml: report the Antora messages also when Antora fails
- build.yml: self-test that check.sh fails on a new Antora error

Anchors are not checked yet, see OpenIdentityPlatform#36.

Refs OpenIdentityPlatform#25
- build.yml: keep a multi-line Antora message in one row of the summary table
- build.yml: self-test check.sh with empty known lists, on a new Antora error
  and a new broken link, and once both are known
- check.sh: escape CR in annotation text as well

Refs OpenIdentityPlatform#25
The OpenAM and OpenDJ docs uploaded since fixed 2 of the 4 known Antora
errors and 32 of the 42 known broken links. Rewritten with
check.sh --update from a build of the current master.

Refs OpenIdentityPlatform#25
@vharseko
vharseko force-pushed the issue-36-37-ci-anchors-meta branch from 842554e to a16ac0a Compare October 2, 2026 07:42
@vharseko

vharseko commented Oct 2, 2026 •

Copy link
Copy Markdown
Member Author

Done in 38afac7 (the branch is rebased onto the current head of #28, a2c1a17):

  • The job summary lists and counts a page/link pair once (sort -u), as check.sh; the number that point to the top of their target page is still taken from the occurrences, as you noted. On a local build: 45 broken, 45 table rows, index.html#j2ee-agent-general-properties once.
  • The anchor cases of the self-test run with empty known lists and require exit 1 and the ::error title=New broken links::… line. d/b.html#b passes as Cannot find fragment and must fail as Cannot find file, so the status guard, split("/") | last and rtrimstr(".html") are all pinned.
  • The head meta / robots.txt check is now .github/build-baseline/head-meta.sh <site>. A new step, "Self-test the head meta check", feeds it a test site: an empty robots.txt, no sitemap.xml, a redirect page, and a page without og:image, with twitter:card twice, that quotes a refresh tag in a listing. It requires exit 1 and exactly those four problems.
  • A redirect page is recognized by <meta http-equiv="refresh" only.

The new steps pass locally, and they fail against each of these mutants: the status guard of top_of_page replaced by true; no filter; every fragment error dropped; the whole path compared instead of the last segment; no rtrimstr; check.sh exiting on the new-entry path before the annotation; in head-meta.sh: the tag count check deleted, -eq 1 → -ge 0, the loose http-equiv="refresh" match, the robots.txt check deleted, the sitemap.xml check deleted, always exit 0. On the real site head-meta.sh reports 285 pages, 0 problems.

Baseline: after #28 dropped the links fixed on master, broken-links.txt has 45 entries: the 10 known links and 35 anchors. Of the 37 anchors in #36, the two .:chap-jee-agent-config.adoc ones are gone from master (OpenIdentityPlatform/OpenAM#1158 turned them into xrefs).

@vharseko
vharseko requested a review from maximthomas October 2, 2026 07:42
Run lychee with --include-fragments. A link to the id of its target
page itself is dropped (Antora renders the page title without an id),
in broken-links.jq, shared by check.sh and the job summary; the
self-test covers both cases. The 35 broken anchors from the product
repositories are added to broken-links.txt (of the 37 in OpenIdentityPlatform#36, the two
.:chap-jee-agent-config.adoc ones are fixed on master).

A new step fails when robots.txt has no Sitemap line for site.url, or
a page does not carry exactly one description, og:title,
og:description, og:image, twitter:card and og:url tag.

Fixes OpenIdentityPlatform#36
Fixes OpenIdentityPlatform#37
- build.yml: list and count a broken link once in the job summary, as
  check.sh does
- build.yml: self-test that check.sh reports a broken anchor and a
  missing page linked at its own id, by exit code and annotation
- head-meta.sh: the head meta and robots.txt check, moved out of
  build.yml and self-tested on a test site
- head-meta.sh: skip only the redirect <meta> of Antora, not a page that
  quotes it

Refs OpenIdentityPlatform#36
Refs OpenIdentityPlatform#37
@vharseko
vharseko force-pushed the issue-36-37-ci-anchors-meta branch from a16ac0a to 38afac7 Compare October 2, 2026 07:47

@maximthomas maximthomas 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.

praise: 38afac7 closes every round-1 item, and the new check has its own self-test.

  • head-meta.sh:43 detects a redirect page by <meta http-equiv="refresh". Asciidoctor escapes that <, and the self-test page quotes &lt;meta http-equiv="refresh" (build.yml:163), so the loose match from round 1 turns the step red.
  • build.yml:99 deduplicates the Links summary with sort -u, so its count and table agree with check.sh:41. The head's Build run has no "No longer found" notice, so both baselines match the build exactly.
  • The anchor cases of the baseline self-test require exit 1 and the exact ::error title=New broken links::… line (build.yml:122-124, :145-148).

@vharseko
vharseko merged commit 52f0bf9 into OpenIdentityPlatform:master Oct 2, 2026
1 check passed
@vharseko
vharseko deleted the issue-36-37-ci-anchors-meta branch October 2, 2026 09:01
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ci Continuous integration, build and publish workflows documentation Improvements or additions to documentation enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

CI: check the head meta tags and the robots.txt Sitemap line in the pull request build CI: check the anchors of links in the pull request build

2 participants