First real commit of site/. Until now outreach/.git tracked exactly one file
(README.md) and every content pack stamped git_sha=5619a40 regardless of what it
shipped — a signature with no provenance behind it. The site had no witness, which
is why a deploy could erase two sessions of work with nothing to diff against and
nothing to restore from.
Tracked here (authored): site/content, site/config, site/i18n, site/public/images,
justfile, scripts/, .just/, run*.sh, rustelo.manifest.toml.
Ignored (reproducible, or secret):
.k, .env real keys — outreach mirrors to a PUBLIC remote
site/r, site/public/r content indexes, rebuilt by `just content`
provisioning/.content-packs 228 MB of deploy tarballs
cache, data, logs_*.out runtime droppings
Three trees are deliberately NOT ignored despite looking generated, because their
declared source cannot currently reproduce them — ignoring them would delete the
only copy:
site/public/images 196 of 209 images exist nowhere else (assets/site is empty)
site/content/{adr,catalog} projections whose generators are unwired (gen-adr-pages)
site/content/domains or failing (`just graph`)
They become ignorable when an audit proves regeneration is a no-op, not before.
Fixes carried in this import
---------------------------
content: the mirror ran one way and reverted its own work. content_processor writes
site/r, but the recipe ended with `rsync -a site/public/r/ -> site/r/`, so each fresh
index was clobbered by the stale deploy-tree copy: the tool reported "index.json with
69 posts" while the disk kept 58. Meanwhile public/r is the tree the deploy pack ships,
so no new content could ever reach production. The two trees own different files, so
the mirror now runs both ways: content indexes r -> public/r (with --delete, or a
renamed slug survives as a live URL), and the four nu artifacts (about, adr-map,
taglines, content_graph) back public/r -> r, excluded from the outbound leg so stale
copies cannot overwrite the fresh originals.
templates: the assembler has two layers (framework defaults, source-project templates)
but the level chain has three (rustelo -> website-htmx-rustelo -> outreach/site). The
site level had no overlay slot, so its template work had nowhere legitimate to live and
ended up in htmx-templates/ — the one tree `just templates` rm -rf's. That is how the
nav submenus were lost. site/templates-overlay/ is the missing third slot; 214 lines
(nav submenus, subscribe CTA, post engagement block) now live in a declared source and
survive regeneration byte-identical.
adr: projected ADR-059..069 into the content tree. They were written months ago and
never reached the site because gen-adr-pages.nu is wired into no recipe — a generator
outside the dependency chain is a generator that does not run.
Gates added
-----------
templates-check reassembles into a temp tree and asserts an empty diff against the
live one: any drift means htmx-templates/ holds work that exists
live one: any drift means htmx-templates/ holds work that exists
nowhere else and the next run deletes it.
Both gates run before the destructive operation, not after. A check you can only run
afterwards is not a gate, it is an autopsy.
Refs: ADR-062 (projection-not-own-repo: site is a Projection of outreach, not its own
repo — different deploy target is not a different visibility boundary), ADR-048
(materialization), ADR-066 (the check decides, never the reporter — which the content
pipeline, the surface that publishes ontoref, was the last place not to honour).
116 lines
3.6 KiB
Text
116 lines
3.6 KiB
Text
#!/usr/bin/env nu
|
|
|
|
# Linkify site conventions in blog markdown source:
|
|
#
|
|
# ontoref / Ontoref → <a href="/about" class="brand-site">…</a> (brand mention, case-preserving)
|
|
# ADR-NNN → <a href="/adr/NNN" class="adr-ref">ADR-NNN</a> (decision deep-link)
|
|
#
|
|
# Operates on the MARKDOWN SOURCE (not the generated HTML) so the change survives
|
|
# every `just content` regeneration. Both rewrites are idempotent via lookarounds:
|
|
# an already-wrapped mention (preceded by `>` / followed by `</a>`) is left as-is.
|
|
#
|
|
# Skipped, so code and metadata never get a stray link:
|
|
# - YAML frontmatter (between the leading --- delimiters)
|
|
# - fenced code blocks (``` … ```)
|
|
# - inline code spans (`…`)
|
|
# - headings (lines beginning with #)
|
|
# - compound identifiers: ontoref-daemon, ontoref/dist, .ontoref, /adr/038 (lookarounds)
|
|
#
|
|
# Usage (from outreach/site):
|
|
# nu scripts/build/linkify-site.nu # default glob
|
|
# nu scripts/build/linkify-site.nu site/content/blog/**/*.md
|
|
# nu scripts/build/linkify-site.nu --check # CI gate, no writes
|
|
|
|
const LINK = '<a href="/about" class="brand-site">${n}</a>'
|
|
const WORD = '(?<![>\w./-])(?<n>[Oo]ntoref)(?![<\w./-])'
|
|
const ADR_LINK = '<a href="/adr/${num}" class="adr-ref">ADR-${num}</a>'
|
|
const ADR_WORD = '(?<![>/\w-])ADR-(?<num>\d+)(?!</a>)'
|
|
|
|
# Apply both site rewrites outside inline-code spans on a single line.
|
|
def linkify-line [line: string]: nothing -> string {
|
|
$line
|
|
| split row '`'
|
|
| enumerate
|
|
| each {|it|
|
|
if ($it.index mod 2) == 0 {
|
|
$it.item
|
|
| str replace --all --regex $WORD $LINK
|
|
| str replace --all --regex $ADR_WORD $ADR_LINK
|
|
} else {
|
|
$it.item
|
|
}
|
|
}
|
|
| str join '`'
|
|
}
|
|
|
|
def main [
|
|
pattern: string = "site/content/blog/**/*.md"
|
|
--check # report posts that WOULD change and exit non-zero; never writes
|
|
] {
|
|
let files = (glob $pattern | sort)
|
|
mut changed = 0
|
|
|
|
for f in $files {
|
|
let lines = (open --raw $f | lines)
|
|
mut out = []
|
|
mut in_front = false
|
|
mut in_fence = false
|
|
mut idx = 0
|
|
|
|
for line in $lines {
|
|
let trimmed = ($line | str trim)
|
|
|
|
# Frontmatter: a leading --- opens it, the next --- closes it.
|
|
if $trimmed == "---" and not $in_fence {
|
|
if $idx == 0 {
|
|
$in_front = true
|
|
} else if $in_front {
|
|
$in_front = false
|
|
}
|
|
$out = ($out | append $line)
|
|
$idx = ($idx + 1)
|
|
continue
|
|
}
|
|
|
|
if ($trimmed | str starts-with '```') {
|
|
$in_fence = (not $in_fence)
|
|
$out = ($out | append $line)
|
|
$idx = ($idx + 1)
|
|
continue
|
|
}
|
|
|
|
if $in_front or $in_fence or ($trimmed | str starts-with '#') {
|
|
$out = ($out | append $line)
|
|
$idx = ($idx + 1)
|
|
continue
|
|
}
|
|
|
|
$out = ($out | append (linkify-line $line))
|
|
$idx = ($idx + 1)
|
|
}
|
|
|
|
let result = ($out | str join "\n")
|
|
let original = (open --raw $f)
|
|
# Preserve a trailing newline if the source had one.
|
|
let final = if ($original | str ends-with "\n") { $result + "\n" } else { $result }
|
|
|
|
if $final != $original {
|
|
if $check {
|
|
print $" ($f | path basename): unlinked site mentions pending"
|
|
} else {
|
|
$final | save -f $f
|
|
print $" ($f | path basename): linked"
|
|
}
|
|
$changed = ($changed + 1)
|
|
}
|
|
}
|
|
|
|
if $check {
|
|
if $changed > 0 {
|
|
error make { msg: $"linkify-site --check: ($changed) post\(s\) have unlinked site mentions — run `just content`" }
|
|
}
|
|
print "linkify-site --check: all posts clean"
|
|
} else {
|
|
print $"linkify-site: ($changed) file\(s\) updated"
|
|
}
|
|
}
|