ontoref-outreach/site/scripts/build/assemble-layers.nu
2026-07-17 01:01:30 +01:00

77 lines
3.3 KiB
Text

#!/usr/bin/env nu
# Resolve a layered override chain into ONE materialised tree.
#
# The chain has three levels and the same rule at every hop: a file present in a
# higher layer WINS; a file absent from it is INHERITED from the layer below.
#
# rustelo (framework) → website-htmx-rustelo (implementation) → outreach/site (consumer)
#
# The implementation already overrides framework pages this way; the consumer is the
# third slot, and until now it did not exist for assets at all — htmx-assets/ was
# refilled from the framework alone, so an override the implementation shipped never
# reached the site.
#
# The cascade is resolved HERE, at build time: the Minijinja loader and the static
# ServeDir each read one path and need no fallback logic.
#
# The output is destroyed and rebuilt on every run, which is the whole point — a file
# that survives only in the output is a file that exists nowhere. Anything the consumer
# owns belongs in its layer (site/_htmx/**), never in the target tree.
#
# Layers are positional, LOWEST FIRST. The base layer must exist; the rest are optional
# (a consumer with nothing to override is a normal state, not an error).
#
# Usage:
# nu assemble-layers.nu <base> [<mid> ...] --out <dir> [--exclude "*.lock.toml,*.md"]
def main [
...layers: string # override chain, lowest layer first; base must exist
--out: string # target tree (destroyed and rebuilt)
--exclude: string = "" # comma-separated basename globs pruned from the result
--label: string = "layers" # name used in the summary line
]: nothing -> nothing {
if ($layers | is-empty) {
error make { msg: "no layers given — at least the base layer is required" }
}
if ($out | is-empty) {
error make { msg: "--out is required" }
}
let base = ($layers | first)
if not ($base | path exists) {
error make { msg: $"base layer not found: ($base)" }
}
# Rebuild from scratch so a file deleted upstream never lingers downstream.
if ($out | path exists) { ^rm -rf $out }
mkdir $out
let applied = $layers | each {|layer|
if ($layer | path exists) {
# `src/.` copies CONTENTS; -L dereferences symlinks (layers may live behind
# links and the tree must be self-contained); cp merges dirs and overwrites
# colliding files — which IS the override semantics.
^cp -RL $"($layer)/." $out
{ layer: $layer, present: true }
} else {
{ layer: $layer, present: false }
}
}
let patterns = ($exclude | split row "," | each {|p| $p | str trim } | where {|p| $p | is-not-empty })
let pruned = $patterns | each {|pat|
let hits = (glob $"($out)/**/($pat)")
$hits | each {|f| ^rm -rf $f }
($hits | length)
} | append 0 | math sum
for l in $applied {
let mark = if $l.present { $"(ansi green)+(ansi reset)" } else { $"(ansi dark_gray)·(ansi reset)" }
let note = if $l.present { "" } else { " (absent — inherited from below)" }
print $" ($mark) ($l.layer)($note)"
}
let count = (^find -L $out -type f | lines | where {|l| $l | is-not-empty } | length)
let prune_note = if $pruned > 0 { $" — ($pruned) pruned by --exclude" } else { "" }
print $"(ansi green)✅(ansi reset) ($label) → ($out) — ($count) files($prune_note)"
}