Skip to content

[#25] CI: build pull requests and make the site build reproducible - #28

Closed
vharseko wants to merge 7 commits into
OpenIdentityPlatform:masterfrom
vharseko:issue-25-ci-pr-check
Closed

vharseko wants to merge 7 commits into
OpenIdentityPlatform:masterfrom
vharseko:issue-25-ci-pr-check

Conversation

@vharseko

@vharseko vharseko commented Sep 30, 2026 •

Copy link
Copy Markdown
Member

Fixes #25, together with #33 — this PR does items 1–4 of it: a reproducible build and a pull request check that fails on new Antora errors and broken links. Item 5, the DocSearch migration, is #33.

Changes

  • publish.yml: npm i antora → npm ci, so the versions in package-lock.json are used.

  • GitHub Actions on Node 24: checkout v7, setup-node v7, configure-pages v6, upload-pages-artifact v5, deploy-pages v5 — the Node 20 versions are deprecated and were forced onto Node 24 with a warning. The only dotfile of the site, .nojekyll, which upload-pages-artifact v4+ leaves out, is not needed by an Actions-based Pages deployment.

  • antora-playbook.yml: the UI bundle was taken from GitLab HEAD with snapshot: true, so any upstream UI change reached production unreviewed. It is now ui/ui-bundle.zip, kept in the repository; the playbook records its source (antora-ui-default job 15385706035, bundle-stable) and sha256. A link to a specific GitLab job is not used because job artifacts expire.

  • publish.yml, antora-playbook.yml: the CDDL license header, Copyright 2024-2026 3A Systems, LLC. (both files have been written by 3A Systems since 2024).

  • build.yml (new): on every pull request to master (and manually) builds the site with the API docs and writes to the job summary:

    • Antora messages: errors and warnings with file and line (errors also as annotations), and missing attribute counts per file; also when Antora itself fails, whose fatal error goes only to the log file;
    • broken links from lychee --offline over the generated HTML, API docs excluded. The anchor of a link is not checked yet: CI: check the anchors of links in the pull request build #36.
  • .github/build-baseline (new): the job fails on an Antora error or a broken link that is not in the known lists — antora-errors.txt (2 known errors) and broken-links.txt (10 known links), all tracked in the product repositories (below). A known one that is gone is reported as a notice so that it is removed from the list. A second occurrence of a known error counts as new. check.sh does the comparison and fails on a missing or malformed build output; check.sh --update rewrites the lists from a build. A self-test step checks that check.sh fails on a new Antora error and on a new broken link, and passes once they are in the lists.

    publish.yml is not gated: one broken docs upload of a product must not stop the publication of the docs of all products; there Antora's messages appear only in the job log, and links are not checked.

Known problems (the baseline)

Problem Tracked in
OpenIG: level-0 section, duplicate id, unterminated open block OpenIdentityPlatform/OpenIG#182
OpenIDM: incomplete table row in appendix-scripting.adoc OpenIdentityPlatform/OpenIDM#232
OpenIDM: 6 broken links to old OpenDJ 3.5 / OpenAM 13, 13.5 docs (404 on the site) OpenIdentityPlatform/OpenIDM#232
OpenIDM: 4 links with one .. too many — they load on the site, because the browser stops at the host root, but break under any path prefix OpenIdentityPlatform/OpenIDM#232

Fixed in the product repositories and gone from master since this PR was opened: the .:chap-jee-agent-config.adoc xrefs (OpenIdentityPlatform/commons#315), the legacy ForgeRock links in OpenAM (OpenIdentityPlatform/OpenAM#1154), OpenIdentityPlatform/OpenAM#1155, the opendj/javadoc links (OpenIdentityPlatform/OpenDJ#1129) and the 3,523 missing attribute warnings of the OpenDJ reference (OpenIdentityPlatform/OpenDJ#1131).

Verification

Run locally with the same commands as the workflow:

  • the site built with the vendored bundle is byte-identical to the one built from the GitLab snapshot, except lastmod in the sitemaps;
  • npm ci installs Antora 3.1.15 from the lock file;
  • report on the current master: 2 errors, 2 other warnings, 31 missing attribute warnings; lychee: 43,108 links checked, 10 broken. Without --fallback-extensions html lychee reports ~24,000 false positives, because the site uses extensionless URLs (html_extension_style: drop);
  • the baseline check passes on master; with a broken xref, a broken link and a stale baseline entry added on purpose it fails with two "New …" errors and one "no longer found" notice;
  • after the review: check.sh exits non-zero without build/ or with a non-JSON log line, and --update then leaves the lists untouched; a repeated known error fails it; % and CR in an annotation are sent as %25 and %0D; a multi-line Antora message stays in one row of the summary table; the self-test step passes against check.sh and fails against it without failed=true, with the comm operands swapped, and with .error_map misspelled.

On GitHub, the Build check of this pull request passes with the baseline check enabled, with no "no longer found" notice, so the lists match the build on the runner.

@vharseko vharseko added documentation Improvements or additions to documentation enhancement New feature or request ci Continuous integration, build and publish workflows labels Sep 30, 2026
@vharseko
vharseko force-pushed the issue-25-ci-pr-check branch from 06ff246 to 8646d34 Compare September 30, 2026 21:28
vharseko added a commit that referenced this pull request Oct 1, 2026
antora 3.1.15 depends on js-yaml ~4.1, and 4.1.1 is affected by
GHSA advisories on quadratic CPU use in merge keys and !!omap
(Dependabot alerts #25-#28). Override it to 4.3.2, the first
release with all fixes; the 4.x API is unchanged and the site
builds with the same known Antora errors as before.

@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 gate is reproducible and its baselines provably match the runner build.

  • ui/ui-bundle.zip hashes to the sha256 recorded in antora-playbook.yml:44 (e0ac81aa…e70d), so the vendored bundle can be checked against its source.
  • .github/workflows/build.yml:86-89 tells lychee's exit 2 (broken links) apart from a failure of the check itself.
  • The Build run on 8646d34 annotates the four known errors with workspace-relative paths and emits no "No longer found" notice, so both lists match the build on the runner.

issue (non-blocking): check.sh passes when its input is missing or malformed, and --update then empties both lists.

.github/build-baseline/check.sh:83-84

"$(antora_errors)" and "$(broken_links)" are arguments to compare, so the non-zero status of jq | sort is discarded and set -e never fires. Without build/ (or with a non-JSON line in antora.log) every known entry becomes a "no longer found" notice and the script exits 0; with --update both files are rewritten to 0 entries, exit 0. In CI the jq calls in build.yml:51 and :93 fail first, so the gate itself holds; the local --update road does not. A plain assignment does stop set -euo pipefail:

errors=$(antora_errors)
links=$(broken_links)
$update || echo "## Compared with the known problems" >> "${GITHUB_STEP_SUMMARY:-/dev/null}"
compare "Antora errors" antora-errors.txt "$errors" "file <TAB> message"
compare "broken links" broken-links.txt "$links" "page <TAB> link"

issue (non-blocking): build/antora.log is appended to across local builds.

.github/workflows/build.yml:44-45

Antora's log file destination appends by default (runtime.log.destination.append: true), and output.clean removes only build/site. Re-running the documented commands locally keeps errors from earlier builds in the log: check.sh still counts an error that is already fixed, and check.sh --update writes it back into antora-errors.txt. CI starts from a fresh checkout and is not affected.

      run: |
        mkdir -p build
        rm -f build/antora.log
        npx antora antora-playbook.yml --log-format=json --log-file=build/antora.log
        npm run copyApiDocs

question (non-blocking): Should a second occurrence of a known Antora error in the same file fail the build?

.github/build-baseline/check.sh:36, :59

The key is file + message without the line number, and both lists go through sort -u. A second target of xref not found: .:chap-jee-agent-config.adoc#configure-j2ee-policy-agent in chap-jetty.adoc collapses into the known key: 0 new, exit 0, while the header says the check "fails on any new one". If collapsing is intended (a partial included from several pages repeats its errors), this needs nothing. If not, comm counts repeated lines, so plain sort keeps the key free of line numbers and still catches the repeat; run check.sh --update afterwards:

    | "\(.file.path // "" | ltrimstr($root))\t\(.msg)"' "$root/build/antora.log" | LC_ALL=C sort
  known=$({ grep -v '^#' "$file" || true; } | LC_ALL=C sort)

suggestion (non-blocking): Nothing in CI exercises the failure path of check.sh.

.github/build-baseline/check.sh:72, .github/workflows/build.yml:101

The Build run only reaches the "0 new, 0 no longer found" path; the failing path was checked once by hand. A mutant that deletes failed=true (line 72), or swaps the comm operands so that new is always empty, keeps every Build run green. The step below passes against the head's check.sh and fails against the mutant without failed=true:

    - name: Self-test the baseline check
      run: |
        t=$(mktemp -d)
        mkdir -p "$t/.github" "$t/build"
        cp -r .github/build-baseline "$t/.github/"
        echo '{"level":"error","msg":"self-test","file":{"path":"'"$t"'/x.adoc"}}' > "$t/build/antora.log"
        echo '{"error_map":{}}' > "$t/build/lychee.json"
        if GITHUB_STEP_SUMMARY=/dev/null "$t/.github/build-baseline/check.sh" > /dev/null; then
          echo "check.sh passed a new Antora error" >&2
          exit 1
        fi

Pin: placed before "Compare with the known problems", it turns red as soon as a new Antora error stops failing check.sh.


suggestion (non-blocking): When Antora itself fails, the job shows no cause.

.github/workflows/build.yml:47

With --log-file, Antora's CLI logs a fatal error to the configured logger — the file, not the console — and "Report Antora messages" has no if:, so it is skipped once "Generate Site" fails. A pull request that breaks the build gets a red check with nothing in the console, the summary or the annotations. The report step already annotates fatal:

    - name: Report Antora messages
      if: ${{ !cancelled() && hashFiles('build/antora.log') != '' }}

suggestion (non-blocking): The link check does not cover anchors.

.github/workflows/build.yml:80-85

lychee 0.24.2 checks fragments only with --include-fragments, and Antora resolves only the page part of an xref. Renaming a section id so that xref:page.adoc#old-id points nowhere gives neither an Antora error nor a broken link, and the check passes. Enabling it may add existing broken anchors to broken-links.txt (not measured):

          --offline --no-progress --format json --include-fragments \

issue (non-blocking): Annotation text is not escaped for workflow commands.

.github/build-baseline/check.sh:70, :76, .github/workflows/build.yml:52

The runner decodes %25, %0D and %0A in command data, so a broken link c%25d.html is annotated as c%d.html, and %0A splits the line. The comparison uses the raw strings and no current entry contains %, so only the annotation text is affected.

    msg=${line//%/%25}
    echo "::error title=New $title::${msg//$'\t'/ — }"

In build.yml:52: \(.msg | gsub("%"; "%25") | gsub("\r"; "%0D") | gsub("\n"; "%0A")).

@vharseko

vharseko commented Oct 1, 2026

Copy link
Copy Markdown
Member Author

Done in 6264be5 (the branch is rebased onto the current master):

  • check.sh fails on a missing or malformed input (assignment first, as suggested); --update no longer empties the lists.
  • build/antora.log is removed before the build.
  • Repeated Antora errors are kept (sort instead of sort -u); the baseline is unchanged, the build has no repeats.
  • The self-test step runs before "Compare with the known problems"; it fails against check.sh without failed=true.
  • "Report Antora messages" runs when Antora fails.
  • Annotation text is escaped.

Anchors: measured with --include-fragments on a local build: 379 more broken links, of which 342 point to the id of the target page itself (Antora renders the page title without an id, so they are harmless) and 37 are real, in OpenAM, OpenDJ and OpenIDM; the check takes 3.5 min instead of 1. This needs a filter for the first kind and issues in the product repositories, so it is #36, not this PR.

@vharseko
vharseko requested a review from maximthomas October 1, 2026 12:18
vharseko added a commit to vharseko/doc.openidentityplatform.org that referenced this pull request Oct 1, 2026
…missing-tag error, no hooks, tar -f -

- lib/versions-from-tags.js: extract the tags into build/antora-tags/<tag>
  instead of a random mkdtemp directory, so that the file paths Antora logs
  for tag content are stable and can be listed in .github/build-baseline (OpenIdentityPlatform#28);
  name the temp branch after the tag
- fail with a hint when a tag is neither local nor on origin (e.g. a fork
  created before the tags were pushed)
- commit in the temp repository with core.hooksPath=/dev/null, so global hooks
  of the developer cannot abort the build
- tar -x -f -: read the archive from stdin explicitly instead of $TAPE
vharseko added a commit to vharseko/doc.openidentityplatform.org that referenced this pull request Oct 1, 2026
…missing-tag error, no hooks, tar -f -

- lib/versions-from-tags.js: extract the tags into build/antora-tags/<tag>
  instead of a random mkdtemp directory, so that the file paths Antora logs
  for tag content are stable and can be listed in .github/build-baseline (OpenIdentityPlatform#28);
  name the temp branch after the tag
- fail with a hint when a tag is neither local nor on origin (e.g. a fork
  created before the tags were pushed)
- commit in the temp repository with core.hooksPath=/dev/null, so global hooks
  of the developer cannot abort the build
- tar -x -f -: read the archive from stdin explicitly instead of $TAPE
vharseko added a commit that referenced this pull request Oct 2, 2026
…nu (#30)

Fixes #22

## Changes
- **`ROOT/modules/ROOT/pages/index.adoc`** (start page):
- an "API Reference (Javadoc)" link at the end of the guide list of each
of the four products;
- a new "API Reference (Javadoc)" section (`#api-reference`) with
*Products* (OpenDJ, OpenAM, OpenIG, OpenIDM) and *Libraries*:
**OpenICF** and **Commons**, whose Javadoc was reachable only by URL.

The links are root-relative (`/openam/apidocs/index.html`), because the
Javadoc is not an Antora page but is copied into the site by `npm run
copyApiDocs`.
- **`supplemental-ui/partials/header-content.hbs`**: a divider and an
"API Reference (Javadoc)" entry in the Projects menu, pointing to that
section.

Component navigation is not changed: the product release workflows
replace `<product>/modules` (`rm -rf`) on every docs upload, so an entry
added there in this repository would be lost; it would have to come from
the product repositories.

## Verification
Local Antora build of this branch (with `copyApiDocs`):
- all six targets (`openam`, `opendj`, `openidm`, `openig`, `openicf`,
`commons` `/apidocs/index.html`) exist in the generated site;
- the menu entry resolves from every page, e.g. `./#api-reference` on
the start page and `../../#api-reference` on `openam/admin-guide/`;
- `lychee --offline`: no broken link on the start page; the site total
is unchanged (45, all known and tracked);
- no new Antora errors;
- the branch is rebased onto `master` after #27 and #35, without
conflicts.

In CI, a missing `<dir>/apidocs/index.html` target is caught by the
lychee check of #28; a renamed `#api-reference` anchor by its
`--include-fragments` run proposed in #36.

@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 round-1 fixes land where the bugs were, and the gate fails closed.

  • .github/build-baseline/check.sh:84-87 assigns both inputs before compare under set -euo pipefail. A missing or malformed antora.log or lychee.json now stops the script instead of reading as an empty build, and --update cannot empty the lists.
  • check.sh:36 and :59 sort without -u and compare with comm, so a second occurrence of a known Antora error counts as new.
  • .github/workflows/build.yml:91 separates lychee's exit 2 (broken links found) from a failure of the check itself.

issue (non-blocking): A multi-line Antora message breaks the "Errors and warnings" table in the job summary.

.github/workflows/build.yml:72

The row escapes only | in .msg; line 56 also escapes CR and LF for the annotation of the same message. With an invalid component antora.yml, Antora exits with a fatal error whose message is "antora.yml has invalid syntax; …" followed by js-yaml's blank line and source snippet (@antora/content-aggregator 3.1.15:718, js-yaml 4.3.2 exception.js). The row splits, and the blank line ends the table. This is the fatal error the step now runs to show (:51). The gate is not affected.

            | "| \(.level) | \(.msg | gsub("\\|"; "\\\\|") | gsub("\r?\n|\r"; "<br>")) | \(.file.path // "" | ltrimstr($ws)) | \(.file.line // "") |"' "$log"

suggestion (non-blocking): The self-test does not catch swapped comm operands, and it never runs the broken-links path.

.github/workflows/build.yml:104-115

The step copies the real baseline, feeds one new Antora error and no links, and passes if check.sh exits non-zero. Measured with mutants of check.sh:

  • Swapping comm -23 and comm -13 (:60-61) keeps the self-test green, because the 4 known errors that are missing from the fixture read as "new". Against the real build, that mutant exits 0 and lets a new error or link through with only a notice.
  • .errormap for .error_map (:41) also keeps it green, and it turns the 42 known links into "no longer found" notices with exit 0.

The step below passes on the head and fails on both mutants and on check.sh without failed=true:

    - name: Self-test the baseline check
      run: |
        # check.sh must fail on a new Antora error and a new broken link, and pass once they are known
        t=$(mktemp -d)
        mkdir -p "$t/.github/build-baseline" "$t/build"
        cp .github/build-baseline/check.sh "$t/.github/build-baseline/"
        : > "$t/.github/build-baseline/antora-errors.txt"
        : > "$t/.github/build-baseline/broken-links.txt"
        echo '{"level":"error","msg":"self-test","file":{"path":"'"$t"'/x.adoc"}}' > "$t/build/antora.log"
        echo '{"error_map":{"/site/p.html":[{"url":"file:///site/q"}]}}' > "$t/build/lychee.json"
        out=$(GITHUB_STEP_SUMMARY=/dev/null "$t/.github/build-baseline/check.sh" 2> /dev/null) && rc=0 || rc=$?
        if [ "$rc" -ne 1 ] \
          || ! grep -qxF '::error title=New Antora errors::x.adoc — self-test' <<< "$out" \
          || ! grep -qxF '::error title=New broken links::p.html — q' <<< "$out"; then
          echo "check.sh did not report the new Antora error and the new broken link (exit $rc): $out" >&2
          exit 1
        fi
        printf 'x.adoc\tself-test\n' > "$t/.github/build-baseline/antora-errors.txt"
        printf 'p.html\tq\n' > "$t/.github/build-baseline/broken-links.txt"
        GITHUB_STEP_SUMMARY=/dev/null "$t/.github/build-baseline/check.sh" > /dev/null

Pin: with empty known lists, the comm swap leaves new empty and the exit code 0, so the step fails. Asserting on the output catches the .errormap mutant.


suggestion (non-blocking): Six of the known broken links load on the live site. They have one .. too many, so they are not legacy paths.

.github/workflows/build.yml:84, .github/build-baseline/broken-links.txt

lychee checks the site mounted at /site. A page-relative link with one .. more than the page is deep leaves /site and resolves to file:///<component>/…. A browser stops the extra .. at the host root. These targets return HTTP 200 on doc.openidentityplatform.org:

  • openam/jee-users-guide/chap-apache-tomcat.html and chap-jetty.html → openam/admin-guide/chap-cdsso
  • openidm/getting-started/chap-where-to-go.html and openidm/samples-guide/chap-ldap-samples.html → opendj/install-guide
  • openidm/samples-guide/chap-fullstack-sample.html → openam/install-guide#configure-openam-custom
  • openidm/samples-guide/chap-workflow-samples.html → opendj/install-guide#gui-install

In the last two, the anchor is defined on another page. The gate is right to fail such a link, since it breaks under any path prefix. Only the label in the description and in OpenIdentityPlatform/OpenAM#1154 and OpenIdentityPlatform/OpenIDM#232 is wrong, and it sends maintainers after pages that load. The fix in the source is one .. less:

* Any LDAPv3-compliant directory, including link:../../opendj/install-guide[OpenDJ, window=\_blank] and Active Directory (see xref:connectors-guide:chap-ldap.adoc#chap-ldap["Generic LDAP Connector"] in the __Connectors Guide__).

That is openidm/modules/getting-started/pages/chap-where-to-go.adoc:93. The openam/13, openam/13.5, docs/openam/13 and openam-*-policy-agents targets return 404 and are really broken.


nitpick (non-blocking): check.sh escapes % but not CR in annotation text. build.yml:56 escapes both, and 6264be5 says both files do.

.github/build-baseline/check.sh:70, :77

A lone CR inside an Antora error message reaches ::error and ::notice raw, and the runner cuts the annotation there. An LF cannot get there, because read splits on it first. No current entry has a CR, and the comparison is not affected.

    msg=${line//%/%25}
    msg=${msg//$'\r'/%0D}

nitpick (non-blocking): The description says that publish.yml reports the problems, but it reports none.

.github/workflows/publish.yml:50

publish.yml runs npm run buildAll, with no --log-file, no job summary and no lychee. On publish, Antora's messages appear only as console lines in the job log, and links are not checked at all. Suggested wording:

`publish.yml` is not gated: one broken docs upload of a product must not stop the publication of the docs of all products; there Antora's messages appear only in the job log, and links are not checked.

- 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-25-ci-pr-check branch from 4b3fbf4 to a2c1a17 Compare October 2, 2026 07:22
vharseko added a commit that referenced this pull request Oct 2, 2026
…wler (#33)

Fixes #25, together with #28 — this PR does item 5, the migration to the
current DocSearch; items 1–4 are #28. The front end moves now, on the
existing index; the crawler stays the legacy scraper until Algolia
Crawler access is set up, for which this PR prepares the configuration.

## Changes
- **Front end: `@docsearch/js` 3.9.0** instead of `docsearch.js` 2
(vendored as before, `supplemental-ui/js/vendor/docsearch.min.js` and
`supplemental-ui/css/vendor/docsearch.min.css`):
- the header shows the DocSearch button, which opens the search modal;
shortcuts `Ctrl/Cmd+K` and `/`;
- same application, key and index (`doc_openidentityplatform`): the
records of the legacy scraper carry the fields the v3 front end reads
(`hierarchy.lvl0–6`, `content`, `type`, `url`), so no reindexing is
needed;
- `transformItems` drops the `#cookie-notice` anchor that the legacy
scraper gives to page titles and to the text before the first section
(the first id on the page, the cookie notice);
- rendered only when the build has both `ALGOLIA_SEARCH_API_KEY` and
`ALGOLIA_APP_ID` (the v3 client requires `appId`); otherwise the
`SITE_SEARCH_PROVIDER` branch (plain search input), which is kept, or no
search.

3.9.0 is the last 3.x; 4.x and 5.x add Ask AI and a side panel, which
this site does not use, and are 4–5 times larger (510 KB and 619 KB of
JS against 133 KB).
- **`docsearch/crawler-config.js`** (new): the same crawl for the
[Algolia Crawler](https://www.algolia.com/doc/tools/crawler/) — records
only from the product pages, as the `start_urls` of `config.json` (the
site home page is crawled for links only), the same exclusions (API
docs, generated references, and the older versions of #31) and the same
selectors for the hierarchy and the content. Not carried over, because
none of it changes the results: the version in lvl0 and the
`desc(version)` ranking (every component has `version: ~`), the
`component` attribute (nothing reads it) and `min_indexed_level`. It
writes a new index, `doc_openidentityplatform_v3`, so the live search is
not affected while it is checked. **Not run yet**: it needs Crawler
access on the Algolia application (for an open-source site, through the
DocSearch program).
- **`docsearch/readme.md`**: how the search is set up, and the steps to
move to the Crawler. The link it held before (how the index was set up
for the legacy scraper) is kept.

`publish.yml` and `config.json` are unchanged: the legacy scraper keeps
reindexing after every deployment.

## Verification
Local build with the public search key of the site, checked in a browser
(Playwright):
- the DocSearch button is rendered in the header, no console errors;
- `Ctrl+K` opens the modal; "session" and "replication" return results
from the live index, grouped by product; page titles link to the page
with no anchor, and no result URL ends with `#cookie-notice` (in the
live index every page-title record does);
- a build with only `ALGOLIA_SEARCH_API_KEY` has neither the button nor
the DocSearch bundle;
- the `pathsToMatch` pattern of `crawler-config.js`, checked with
micromatch, matches `/openam` and `/openam/` but not `/`, `/openicf/…`
or `/commons/…`;
- rebased onto `master` (with #29); merges with #30 and #32 without
conflicts (`git merge-tree`).
@vharseko vharseko added the build Site build: Antora playbook, extensions, scripts label Oct 2, 2026
@vharseko

vharseko commented Oct 2, 2026

Copy link
Copy Markdown
Member Author

Done in c7ef1cd (the branch is rebased onto the current master):

  • The "Errors and warnings" row turns CR/LF in a message into <br>, as suggested; checked with a multi-line fatal message.
  • The self-test step is the suggested one: empty lists, a new Antora error and a new broken link, then both known. It passes on check.sh and fails against the three mutants (no failed=true, swapped comm operands, .errormap).
  • check.sh escapes CR in the annotation text as well.
  • The description: the publish.yml wording is yours.

The six links with one .. too many: the two OpenAM ones are gone from master (OpenIdentityPlatform/OpenAM#1158 turned them into xrefs). The four OpenIDM ones are listed apart in the description; the fix proposed in OpenIdentityPlatform/OpenIDM#232, a cross-component xref, holds for them too.

The rebase onto master also changed the baseline: the OpenAM and OpenDJ docs uploaded since then fixed 2 of the 4 known errors and 32 of the 42 known links, so a2c1a17 removes them (check.sh --update on a local build; 0 new). The table of known problems in the description is updated accordingly.

@vharseko
vharseko requested a review from maximthomas October 2, 2026 07:26

@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 self-test closes the gap from the previous round, and it fails for the right reason.

  • .github/workflows/build.yml:114-117 captures check.sh's stdout and checks the exact ::error lines with grep -qxF, not just the exit code, so the step fails on all three mutants named last round.
  • .github/build-baseline/check.sh:70-71 escapes % before CR, which is the order the runner unescapes them in. build.yml:72 keeps a multi-line fatal Antora message in one summary row.
  • The Build run at a2c1a17 passes with the shrunk baseline and shows no "No longer found" notice.

suggestion (non-blocking): The self-test never makes check.sh print a "No longer found" notice.

.github/workflows/build.yml:104-123, .github/build-baseline/check.sh:61, :80

Pass 1 runs with empty known lists and pass 2 with known == build, so fixed is empty both times. Pass 2 also discards stdout. Three mutants keep the step green: comm -13 → comm -12 at check.sh:61, fixed=, and ::notice → ::debug at :80. Under the first one, every known entry that is still present gets announced as "no longer found", and an entry that really is stale does not. The gate stays green either way, because notices never set failed=true. This step is the pin suggested in the previous round, so the gap is the reviewer's.

printf 'x.adoc\tself-test\ny.adoc\tgone\n' > "$t/.github/build-baseline/antora-errors.txt"
out=$(GITHUB_STEP_SUMMARY=/dev/null "$t/.github/build-baseline/check.sh" 2> /dev/null) && rc=0 || rc=$?
if [ "$rc" -ne 0 ] \
  || [ "$out" != '::notice title=No longer found; remove from .github/build-baseline/antora-errors.txt::y.adoc — gone' ]; then
  echo "check.sh did not report only the stale known error (exit $rc)" >&2
  exit 1
fi

Pin: append this after build.yml:123. A stale known entry must give exactly one notice and exit 0. It passes on head and fails on all three mutants.


suggestion (non-blocking): No test feeds a CR, so the new %0D escape can be deleted without any test failing.

.github/build-baseline/check.sh:71, :79, .github/workflows/build.yml:72

The self-test's message is self-test, which has no CR or %. Deleting msg=${msg//$'\r'/%0D} at :71 and :79 keeps the step green. The code at head is correct: 50%\r becomes 50%25%0D. The pin below catches the deletion at :71. The notice copy at :79 and the <br> replacement at build.yml:72 remain untested.

echo '{"level":"error","msg":"50%\r","file":{"path":"'"$t"'/c.adoc"}}' > "$t/build/antora.log"
out=$(GITHUB_STEP_SUMMARY=/dev/null "$t/.github/build-baseline/check.sh" 2> /dev/null) && rc=0 || rc=$?
if [ "$rc" -ne 1 ] || ! grep -qxF '::error title=New Antora errors::c.adoc — 50%25%0D' <<< "$out"; then
  echo "check.sh did not escape % and CR in the annotation (exit $rc)" >&2
  exit 1
fi

Pin: append this as the last block of the step, since it replaces the Antora log. It passes on head and fails once the CR escape is deleted.


issue (non-blocking): When the self-test fails, echoing check.sh's output turns its ::error lines into PR annotations.

.github/workflows/build.yml:118

$out holds check.sh's workflow commands, and line 118 sends it to stderr. The runner parses stderr for commands too: ScriptHandler.cs:336 hands stderr to an OutputManager, and OutputManager.cs:87 calls TryProcessCommand (actions/runner). The first line of $out follows the message prefix on the same line, so it stays plain text. Every line after it becomes an annotation, for example ::error title=New broken links::p.html — q, which names a page and a link that do not exist. This only happens when the self-test is already red, so the job still fails, but the annotation points at the wrong thing. Leading spaces do not prevent it, because the runner trims them before looking for ::.

echo "check.sh did not report the new Antora error and the new broken link (exit $rc); its output:" >&2
sed 's/^/> /' <<< "$out" >&2

note (non-blocking): The description no longer matches the diff.

  • antora-playbook.yml: "Changes" still says this PR adds the CDDL header here. BASE already has Copyright 2024-2026 3A Systems, LLC. at line 13 (872dec2), so only .github/workflows/publish.yml gets the header in this PR.

vharseko added a commit that referenced this pull request Oct 2, 2026
… Sitemap line (#38)

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 #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 #36 but the two
`.:chap-jee-agent-config.adoc` ones, which
OpenIdentityPlatform/OpenAM#1158 has fixed on `master`. The 10 known
links of #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 commented Oct 2, 2026

Copy link
Copy Markdown
Member Author

Closing: everything in this pull request is already on master. #38 was stacked on its head a2c1a17 and was squash-merged as 52f0bf9, so antora-playbook.yml, ui/, publish.yml and .github/ on master match this branch, plus the additions of #38.

The third review round is done in #40 (issue #39), from master, since the code it touches now lives there, including the parts #38 added:

  • The self-test checks the "No longer found" path: a stale known entry must give exactly one notice and exit 0.
  • The self-test feeds 50% + CR, to a new entry and to a stale one, and expects 50%25%0D in both the ::error and the ::notice line.
  • On failure, the output of check.sh and head-meta.sh goes to stderr quoted with > , so that its ::error lines do not become annotations. This applies to all three places: the two in the baseline self-test and the one in the head meta self-test that [#36] [#37] CI: check link anchors, head meta tags and the robots.txt Sitemap line #38 added.

The note on the description is correct: antora-playbook.yml already had Copyright 2024-2026 3A Systems, LLC. on the base (872dec2), so only publish.yml got the header here.

@vharseko vharseko closed this Oct 2, 2026
@vharseko

vharseko commented Oct 2, 2026

Copy link
Copy Markdown
Member Author

@maximthomas, on the third round (review 5389582272): done in #40, since this PR is already on master through #38. Please review there.

  • "No longer found" path: added your block. It fails on comm -12, fixed= and ::debug.
  • CR escape: added your block, extended to a stale entry. It expects 50%25%0D in both the ::error and the ::notice line, and fails when the escape is deleted from either one.
  • Quoted output on failure: sed 's/^/> /' in all three places, including the two that [#36] [#37] CI: check link anchors, head meta tags and the robots.txt Sitemap line #38 added; no stderr line of a failing self-test starts with ::.
  • The description note: correct, the playbook header was already on the base (872dec2).

vharseko added a commit that referenced this pull request Oct 2, 2026
…r version (#31)

Fixes #26

Publishes the last release of each previous major line as an older
version; `master` stays the default version, so no URL changes.

| Product | `master` (unchanged URLs) | Older version |
|---|---|---|
| OpenAM | `/openam/…`, shown as **16.x** | `/openam/15.2/…`, **15.2.2**
(tag `OpenAM-15.2.2`) |
| OpenDJ | `/opendj/…`, **5.x** | `/opendj/4.10/…`, **4.10.2** |
| OpenIDM | `/openidm/…`, **7.x** | `/openidm/6.3/…`, **6.3.0** |
| OpenIG | `/openig/…`, **6.x** | `/openig/5.3/…`, **5.3.2** |

The version menus of the default UI (component list and the per-page
version menu) show the older versions as soon as a component has more
than one.

## Why an extension (`lib/versions-from-tags.js`)
Listing the tags as content sources in the playbook is not enough:
1. the `antora.yml` of every tag declares `version: ~`, and Antora gives
the version in `antora.yml` precedence over the version of the content
source, so a tag would be merged into the unversioned component version
built from `master`;
2. Antora reads **every** file under the start path of a git tree, and
at a tag that includes the API docs of that time (18,672 files for
`OpenAM-15.2.2`, against 330 pages): the build took 4m14s instead of
43s.

The extension replaces `aggregateContent`: the branch sources are
aggregated as before; for each tag source it extracts only
`<product>/antora.yml` and `<product>/modules` with `git archive` into a
temporary repository under `build/antora-tags/<tag>` (a fixed path, so
that the errors Antora logs for tag content can be listed in the
baseline of #28), aggregates it separately and sets the version from the
tag name (`OpenAM-15.2.2` → version `15.2`, display `15.2.2`). The build
now takes about 1m16s locally. If the tag is missing — as in the shallow
`actions/checkout` of CI — the extension fetches it from `origin` (with
`--depth=1` in a shallow repository), so no workflow change is needed.
If the fetch fails (e.g. a fork created before the tags were pushed, or
no network), the build fails with the `git fetch` command that gets the
tag from the upstream, followed by git's own error. The temporary commit
runs with `core.hooksPath=/dev/null`, so global git hooks of a developer
cannot abort the build.

## Other changes
- `<product>/antora.yml`: `display_version` of `master` (16.x, 5.x, 7.x,
6.x).
- `lib/add-pdf-link.js`: the "Download PDF" link only on the latest
version — the wiki PDFs are built from the current sources.
- `lib/add-pdf-link.js`, `<product>/antora.yml`: CDDL header with
`Copyright 2024-2026 3A Systems, LLC.` — these files had no copyright
line; the playbook got the same line in #29.
- Tag sources have `edit_url: false`: no "Edit this Page" on older
versions.
- `docsearch/config.json`: older versions
(`/<product>/<major>.<minor>/`) added to `stop_urls`, so search does not
mix them with the current docs.

## Verification
Local builds:
- the 285 `master` pages differ from the current build only by the older
versions added to the version menus; the start page still links to
`master`;
- the canonical URL of an older page points to the same page in `master`
(e.g. `…/openam/admin-guide/`); no PDF or edit link on older pages;
- CI scenario: a `--depth 1` clone without tags — the extension fetched
the 4 tags and the build succeeded;
- rebased onto `master` with #27 and #29 merged: the only conflict, the
copyright line of `antora-playbook.yml`, resolved to `master`; merges
with #28 without conflicts;
- a `--depth 1 --no-tags` clone whose `origin` is unreachable: the build
fails with `cannot fetch tag OpenDJ-4.10.2 from the origin of …; if the
tag is missing there, run git fetch …`, then git's `Couldn't connect to
server`;
- the OpenIG-5.3.2 copy of the known `sec-release-levels.adoc` error is
logged as
`build/antora-tags/OpenIG-5.3.2/openig/modules/ROOT/partials/sec-release-levels.adoc`
with refname `OpenIG-5.3.2`, the same on every run;
- site size with API docs: **877 MB** (GitHub Pages limit: 1 GB);
- `lychee --offline`: 106 broken links instead of 45. The new ones are
the same legacy ForgeRock-era links inside the frozen tags, plus 8 links
from `openam/15.2` pages to `openam/15.2/apidocs` — the API docs are
published for the current version only.

## Merge order with #28
#28 adds a gate that fails a pull request on an Antora error or a broken
link missing from `.github/build-baseline`. This PR adds one Antora
error (the OpenIG-5.3.2 copy above) and the new broken links of the
older versions. Whichever of the two lands second runs
`.github/build-baseline/check.sh --update` on its build and commits both
lists.

## Not in this PR
- When a new major line is released (e.g. OpenAM 17), the tag in the
playbook and `display_version` are updated by hand.
- The stray tag `${TAG_NAME}` (`0abcd92e4f`) mentioned in #26 is not
deleted yet; deleting a tag on `origin` is done separately.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

build Site build: Antora playbook, extensions, scripts 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: fail the build on broken xrefs/links and make the build reproducible

2 participants