hyperhive/nix/packages/reference-docs.nix
atlas 54daf9ac60 ci: gate documentation pointers so a dead one fails the build
Pointing at a doc instead of duplicating its prose next to the code is what
the comment-block lint pushes people toward, and nothing checked that the
pointers still resolve. The reorg that prompted the preceding commit broke
41 references and every one of them merged green.

Two arms: repo-root-relative `docs/` paths, and relative markdown links
resolved against the linking file. Scope is every tracked file rather than a
type list — the dead references were in css, html and an ignore file as well
as in markdown and nix.

Three controls, because a link checker whose extractor quietly matches
nothing passes forever while checking nothing: each arm must have found
candidates, and at least one pointer must have resolved. Any of those failing
exits non-zero and says the script is broken rather than the tree.

Escape hatch is the `lint:allow` marker the tracker-tag lint already uses.
Its only current users are in reference-docs.nix, where a path naming a file
the derivation synthesizes into $out is correct precisely because the repo
does not have it.

Also runs in the pre-push hook alongside the other two.
2026-09-02 10:23:24 +02:00

106 lines
4.3 KiB
Nix

{
stdenv,
lib,
self,
optionsMd,
}:
# The repo `docs/` markdown tree, shipped as a standalone derivation so
# agents can read the reference docs in-container (added as a claude
# additional directory by the harness) WITHOUT pulling the branding +
# prompt assets they don't need, and so the `hyperhive/website` repo can
# reuse the exact same tree as a flake input.
#
# The docs/ tree is copied verbatim. Two more subdirs are virtualized on
# top of it, same reasoning for both: project an external source of
# truth into the tree instead of hand-copying (and drifting) it.
# `docs.nix` (website repo) needs no changes to pick either up — it
# already walks every subdirectory generically.
#
# For crates that is `$out/crates/<crate>.md`, one per workspace crate
# with a README.md (mara's ask: "behaves as if hive-core/README.md lives
# at docs/crates/hive-core.md" — lint:allow, a path this derivation
# creates in $out, so the repo has no such file). The crate's own README
# stays the single source of truth.
#
# Output layout:
# $out/ — the repo docs/ tree verbatim
# $out/crates/<crate>.md — one per workspace crate README
# $out/options/*.md — the nixosOptionsDoc bundle (`packages.docs`,
# nix/docs/default.nix), copied verbatim —
# already self-contained CommonMark, unlike
# the crate READMEs below, so no rewriting.
let
cargoToml = builtins.fromTOML (builtins.readFile ../../Cargo.toml);
members = cargoToml.workspace.members;
# A member-name relative link (`../<other-crate>`, no specific file —
# forge's browser renders that as a directory listing today) becomes a
# sibling crates/<other-crate>.md link once every crate README
# lives flat in the same directory. Built once, applied per file below,
# rather than a fresh `lib.concatMapStrings` per crate's own transform —
# every crate needs the exact same substitution list (every *other*
# member), so this only differs on the `member != other` filter.
siblingLinkFixups =
member:
lib.concatMapStrings (
other:
lib.optionalString (other != member) ''
-e 's,\]\(\.\./${other}(/)?\),](./${other}.md),g' \
''
) members;
in
stdenv.mkDerivation {
pname = "hyperhive-docs";
version = "0.1.0";
# Narrow src (just docs/) keeps the *base tree*'s input hash decoupled
# from the rest of the workspace — a doc edit only re-hashes this half.
# The crates/ generation below necessarily widens that: it reads
# each crate's own README.md via `self`, so this derivation's hash now
# also tracks the whole repo tree (Nix has no cheaper way to depend on
# "just these few files" out of a flake `self`). Accepted trade-off —
# the alternative is hand-copying READMEs into docs/ and letting them
# drift, which is the exact duplication this issue exists to avoid.
src = ../../docs;
inherit self optionsMd;
dontBuild = true;
dontConfigure = true;
installPhase = ''
runHook preInstall
mkdir -p $out
cp -r ./* $out/
mkdir -p "$out/crates"
cp -r "$optionsMd" "$out/options"
${lib.concatMapStrings (member: ''
if [ -f "$self/${member}/README.md" ]; then
# Rewrite relative links that were correct from the crate's own
# position in the repo tree but aren't once the file is
# virtually one level under docs/ instead:
# ../docs/x.md (crate root -> repo docs/) -> ../x.md lint:allow (stand-in name)
# (crates/ -> docs/ is one directory shallower than
# <crate>/ -> docs/ was, so the leading docs/ segment drops)
# ../<sibling-crate> (that crate's own directory, no file)
# -> ./<sibling-crate>.md, once that sibling also has a
# crates page every crate README lives flat beside it.
sed -E \
-e 's,\]\(\.\./docs/,](../,g' \
${siblingLinkFixups member} \
"$self/${member}/README.md" \
> "$out/crates/${member}.md"
fi
'') members}
runHook postInstall
'';
dontFixup = true;
meta = {
description = "hyperhive reference docs (the repo docs/ tree, plus virtual crates/<crate>.md and options/*.md subdirs)";
homepage = "https://forge.darkest.space/hyperhive/hyperhive";
};
}