Deep reference for the site build script and Quartz. Read this when you need to edit what the site includes, add a redaction, or debug a build. The main flow is in SKILL.md.
- The build script
- Config lists — what to edit
- Transforms the script applies
- Redaction (public-site safety)
- Quartz / build gotchas
- Commands
The build script
garden/scripts/build-site-content.mjs is first-party, dependency-free Node. Run from the repo root: node garden/scripts/build-site-content.mjs. It wipes and rebuilds garden/content/ from repo source files, then Quartz turns garden/content/ into garden/public/.
All the lists below are declared at the top of the script for auditability. Editing the site’s contents = editing one of these lists (usually copying an existing entry). After any edit, re-run the script and preview.
Config lists — what to edit
MAPPINGS — whole directories of markdown + assets
Each entry recursively copies .md and images/PDFs from a source dir to a site folder, preserving subfolders (so ![[img.png]] embeds resolve).
{ src: "obsidian/configs", dest: "configs" },- Add a new top-level content area: add one entry, e.g.
{ src: "snippets", dest: "snippets" }. - Optional keys:
readmeAsIndex: true(a folder’sREADME.mdbecomes its landing page),indexBasename: "SKILL.md"(named file becomes the folder index). - Asset types copied automatically:
.png .jpg .jpeg .gif .svg .webp .pdf(seeASSET_EXT). - Dirs never entered:
.git .obsidian .claude node_modules dist build coverage test src scripts syntaxes language-configuration themes .vscode(seeSKIP_DIRS)..claudeis skipped by the walker, so.claude/skillsis mapped explicitly.
CODE_FILES — single config files as highlighted code pages
Each entry renders one file inside a fenced, syntax-highlighted block (Shiki) wrapped in a small page.
{ src: "dotfiles/git/.gitconfig", dest: "dotfiles/gitconfig.md", lang: "ini", title: ".gitconfig" },- Add a new dotfile / config page: copy a line, set
src,dest(always.md),lang(Shiki language id, e.g.ini,toml,json,hcl,bash), andtitle.
CODE_DIRS — a directory of config files, by extension
Walks a dir recursively and renders every file with a matching extension as a code page. Used for the Terragrunt blueprint’s .hcl files.
{ src: "blueprints/01-terragrunt", dest: "blueprints/01-terragrunt", langByExt: { ".hcl": "hcl" } },- New
.hclunder that dir → automatic. To cover another extension, add tolangByExt(e.g.".sh": "bash").
DOWNLOADS — binaries offered for download
Copies matching binaries as static assets and emits a small page linking to them.
{ src: "books/golang", dest: "books/golang", exts: [".epub"], title: "Learning Go", blurb: "…" },SINGLE_FILES — one-off files with special handling
obsidian/MOC.md → MOC.md, and README.md → index.md (the home page, home: true triggers link rewrites + H1 strip). Rarely needs changes.
Transforms the script applies
Per markdown file, in order:
- Tags line → frontmatter. The vault’s line-1
tags: #a #bbecomes real YAMLtags:frontmatter so Quartz renders tag chips. Files that already start with---are left as-is. - Redactions (see below) — applied to file contents and to generated dest paths (
redactPath), so a redacted value can’t leak via a URL either. - Home links (README only) — rewrites repo-relative links (
obsidian/…,vscode/plugins/harp/…, the dotfiles) to site paths viaHOME_LINK_REWRITES, and strips the# configsH1.
Code pages get a > [!info] header showing the source path, then the file body in a fence sized to survive any backticks inside.
Redaction (public-site safety)
REDACTIONS is a list of [from, to] string replacements applied everywhere in the public output. Each entry is something deliberately kept off the public site; the private repo keeps the real value.
const REDACTIONS = [
["123456789012", "123456789012"], // real AWS account id → docs placeholder
["123456789012", "123456789012"], // HashiCorp tutorial acct id in notes
["you@example.com", "you@example.com"], // personal email
];To redact a new secret: add a [from, to] pair, re-run the script, then confirm zero leaks: grep -r '<secret>' garden/public must return nothing. Prefer redaction over deleting content — the note stays useful, the secret doesn’t ship.
Quartz / build gotchas
garden/content/must NOT be in.gitignore. Quartz’s file discovery uses globby withgitignore: truehardcoded, so a.gitignoreentry would make it skip the generated content (0 input files). It’s kept out of git via.git/info/excludeinstead (which globby does not read).garden/public,node_modules,.quartz,.quartz-cacheare in.gitignore.- The
install-pluginsnpm script is broken (it references the old v4externalPluginsand runs via tsx, which dies on.scss). Never runnpm run buildeither — itsprebuildhook calls that broken script. The correct v5 command isnpm --prefix garden run quartz -- plugin install(populatesgarden/.quartz/plugins/fromquartz.lock.json). Needed after a fresh clone or whenquartz.lock.jsonchanges; the build won’t compile without it (Head.tsximports../../.quartz/plugins). - Config is YAML, not merged.
garden/quartz.config.yamloverridesquartz.config.default.yamlwholesale — a custom file must be complete. Site title,baseUrl(configs.themaybe.uk), analytics-off, and the color palette live here. - CSS overrides →
garden/quartz/styles/custom.scss(committed, imported bycomponentResources.ts). Never editgarden/.quartz/plugins/**— git-ignored and regenerated byplugin installevery build, so edits vanish. High-specificity selectors there win over plugin CSS regardless of bundle order. - Run commands as
npm --prefix garden run quartz -- <cmd>(cwd resolves togarden/, output togarden/public). Permission rules in.claude/settings.local.jsonallow this form plusnode garden/scripts/build-site-content.mjs.
Commands
# regenerate site content from repo source (run from repo root)
node garden/scripts/build-site-content.mjs
# build + live preview at localhost:8080
npm --prefix garden run quartz -- build --serve
# build only (CI does this)
npm --prefix garden run quartz -- build
# after a fresh clone: fetch community plugins (from quartz.lock.json)
npm --prefix garden run quartz -- plugin install
# stop the preview server
lsof -ti tcp:8080 | xargs -r kill