Hwaro::Core::Build::Builder
Inherits Hwaro::Core::Build::Phases::Finalize < Hwaro::Core::Build::Phases::Write < Hwaro::Core::Build::Phases::Generate < Hwaro::Core::Build::Phases::OutputFormats < Hwaro::Core::Build::Phases::Render < Hwaro::Core::Build::Phases::Transform < Hwaro::Core::Build::Phases::ParseContent < Hwaro::Core::Build::Phases::ReadContent < Hwaro::Core::Build::Phases::Initialize < Hwaro::Core::Build::ShortcodeProcessor < Reference < Object
Constants
load_data(path="static/prices.json") / load_data("…") with a
literal path — the only form a template scan can resolve.
Constructors
Instance methods
True when a watcher-relative path (e.g. "static/js/app.js") is a
source file of a configured [assets] bundle. The serve watcher uses
this to know a plain (non-Sass) static save must re-run the bundle
pipeline — bundle inputs are source_dir-joined bundle.files,
exactly how Assets::Pipeline reads them.
Record an output file this build produced that no cache entry
covers. Public: the taxonomy generator is a module that reaches the
builder through its builder: argument.
static_copy marks the Initialize phase's copy of static/: it is
a claim like any other, but not a generator's (see
@generator_output_claims); content_copy marks the Write phase's
content copies (see @content_copy_claims).
Start this page's list over — called at the top of every render so a
serve session's repeated rebuilds don't accumulate.
The most recently loaded site config (nil before the first build). The serve watcher reads it to diff restart-only [serve] settings after a config-triggered rebuild.
Access build context for external inspection (e.g. emitting JSON
output after a build). Returns nil before run has been invoked.
Republish non-Markdown content assets (images, etc.) the way the full build's Write phase does, but only the files the watcher flagged:
[content.files]copies go to their path relative tocontent/(minified likeprocess_raw_fileswhenminify), skipping extensions the config doesn't permit so the watcher can't smuggle a.mdor a disallowed type into output;- page-bundle assets go to the owning page's URL, whether or not
[content.files]is on and wherever a slug / path / permalink moved the page (process_assets).
@config is nil only before the initial build, which
Server#run_with_options already performs before spawning the
watcher, so the watcher always sees a loaded config.
Copy only the specified static files to the output directory. Used by serve mode when only static files have changed.
Returns true when one of the copies would land on a file a PAGE owns
(static/about/index.html beside a page whose url is /about/) or
a generator writes (static/robots.txt, static/404.html, a
taxonomy page, an alias stub — see generated_output_paths). The
static-only rebuild strategy re-renders nothing, so the static
bytes would sit on that URL for the rest of the session; the caller
escalates to a full rebuild, where the render and the generators
run after the copy and win — exactly the cold-build outcome. Such a
file is not copied here; the full rebuild's static copy publishes it.
True when a user feed template override is loaded. Also consulted by the Generate phase's skip-if-unchanged gate: a template-only edit doesn't touch content, so a warm --cache build would keep the stale feed on disk if feeds were skipped.
Renderer over user feed templates (templates/rss.xml.jinja /
atom.xml.jinja, loaded under the keys "rss.xml"/"atom.xml"). Nil
when no override template exists so the feed generators keep the
zero-cost programmatic path. The proc renders with a FRESH Crinja
environment per call — the shared env is not MT-safe (with_scope
mutates the env) and the SEO tasks that generate feeds run in
parallel fibers. create_fresh_crinja_env carries the snapshot
template loader, so {% include %} inside a feed template works.
Once something generated over a copied path, it no longer holds the static file's bytes (the serve lazy OG handler).
True when a modified template feeds a [[content.generate]]
body_template — the body template itself, or a partial it
(transitively) includes. Bodies are rendered while the content is
GENERATED (Read phase), so only a full build re-runs them: the
re-render strategies re-render pages from the bodies they already
hold, and the body file (often .md) is not even in the template
snapshot, so the edit read as "contents are identical".
Inline template specs (body = "{% include 'partials/x.html' %}",
a templated description, …) render at that same point, so a
partial THEY include counts too.
changed_paths are watcher paths (templates/gen/product.md).
Are there any pages stashed by --fast-start waiting to render?
Server checks this to decide whether to spawn the background fiber.
True when one of paths (watcher paths) was read by an include call
or a transclusion: the pages that include it must re-render.
True when the include source path no longer holds the bytes the
last build read (digest is the watcher's MD5, nil when missing).
Project-relative paths every include call and transclusion has read since the last full build (see Builder@include_sources).
True when a template reads one of paths (watcher paths such as
static/prices.json) through a literal load_data() path. Files
under data/ take the full-rebuild lane already; one anywhere else
rides the static or content-asset lane, which only copies it, so
every page printing the data kept the pre-edit values. A path built
at render time (load_data(path=page.extra.file)) is not seen.
Every literal load_data() path in the templates, normalized. The
watcher stamps the ones outside its roots (extra/menu.json) like
include sources, or editing one never rebuilt anything.
Stamp the start of this build's output writing (see
@build_output_epoch).
Record an output file the static copy just wrote over (see
@static_copied_outputs). Public because the serve watcher's
static-only lane (copy_changed_static) copies through the builder
too and must record what it wrote for the same reason. path must
already be canonical — see static_copied_output?.
True when one of paths (project-relative, as the watcher reports
them) is the logo or background image that auto OG images embed and
whose digest keys their manifest. The static / content-file lanes
copy bytes only, so serve escalates to a build that regenerates the
images. Never true where no image is generated eagerly (disabled,
--skip-og-image, lazy serve mode regenerates on request).
The logo and background image auto OG images embed, as configured.
The watcher stamps the ones outside its roots (brand/logo.png).
Every output file the CURRENT site claims — primary page outputs plus output-format siblings, computed exactly like collect_page_output_paths so the two can never drift. The serve watcher filters its pre-rebuild stale list through this AFTER the rebuild: a source deleted and re-created under a different path in one changeset (foo.md → foo/index.md) maps to the same output file, which the rebuild just rewrote and must not be deleted.
The site the build before the current one produced (serve mode
only). A full rebuild replaces site with a fresh one whose pages
learn their lazy og:image URLs only in BeforeRender; until then the
lazy OG handler answers from these pages instead of 404ing.
Held only while a rebuild runs, and only with lazy OG images on.
[privacy]: point html's external assets at local copies. The
files they now load are claimed, and recorded against owner (a
page path) so a --cache hit, which skips the render, keeps them.
Public: the taxonomy generator writes through it too.
[privacy]: the local URL for one external URL (the PWA precache
list), or nil to keep it external.
The one choke point every serve prune deletes through. candidates
are files an earlier pass wrote that the bookkeeping of THIS pass no
longer names; each is deleted only when nothing in the current
output still stands on it:
- a page output of the current site (
owned_output_paths); - an alias stub / pagination page any page's render recorded;
- a generated-output claim — only while the claim set is this
build's own (
@generated_claims_current): an incremental pass re-claims nothing, so there the set still names the very files being pruned; - a file this pass wrote (
written_this_build?— every serve pass re-stamps the epoch, seebegin_serve_pass); - a live
static/orcontent/file that publishes at that path, which is what an incremental pass cannot learn from its claims. A static source's bytes are copied back over the stale file — it was shadowing them, and a cold build publishes them there.
Paths are compared through kept_output?, which also treats a spelling
that differs only by case as kept when it is the same file on disk
(a case-folding filesystem: APFS, NTFS).
Recompile all SCSS entries into the output directory. Used by
serve mode when a .scss source changes — such files publish as
compiled .css, never verbatim. No-ops unless [sass] is enabled.
Returns true when reprocessing the asset bundles changed the manifest (a fingerprinted filename moved) — see reprocess_asset_bundles for what callers must do then.
Record an alias stub / pagination page / localized [privacy]
file page just wrote.
Rewrite manifest.json + sw.js for a serve pass, like the full build's
PWA hook (same [privacy] localizer, or precache URLs revert to the
external ones). Public: serve's static / content-file lanes call it
after copying bytes sw.js content-hashes. Warn-and-continue — a
transient failure must not skip cache.save / the reload.
content/ source paths minus the files of withheld (draft / future /
expired) bundles — see Phases::Write#withheld_content_file?.
Render pages that were skipped on the initial --fast-start build.
Runs after the dev server is already serving the priority subset,
so user-visible "ready" time stays bounded on large sites.
Regenerates SEO/search files at the end since feeds and the search
index pull from page.content, which was empty for deferred pages
during the initial Generate phase.
Also runs the BeforeRender hooks for the remaining work the cold pass deferred — OG image generation for non-priority pages and image resizing for static/content_file globs + non-priority page assets. Without this step those images never get produced in a fast-start serve session until the user saves a file.
Re-run the asset pipeline so SCSS (or plain CSS/JS) bundle sources refresh under serve. Updates the AssetHooks class-level manifest so subsequent renders see new fingerprint paths.
Returns true when the manifest changed — with fingerprinting on, every already-rendered page still references the OLD hashed filename, so callers must follow up with a page re-render.
Full build. The incoming struct is stored on the BuildContext
verbatim, so every field (full, serve_mode, …) reaches the
phases and hooks that branch on them (--full cache clearing,
[og.image] lazy_generate under serve).
Returns false when the build failed without raising (pre-hook failure or a phase abort) — the serve watcher branches on this to surface the failure instead of live-reloading onto a broken site.
Incremental build: only re-parse and re-render pages whose source files have been modified. Falls back to a full build when the necessary state from a previous build is not available.
Optimizations over a full build:
- Only re-parses changed files (not all pages)
- Diff-based taxonomy update (not full rebuild)
- Re-links navigation only for affected sections
- Recomputes series/related posts only for affected pages
- Selectively invalidates Crinja caches
Incremental parse of changed content + full re-render with reloaded templates. Used when both content and templates changed simultaneously.
Re-render pages using reloaded templates without re-parsing content. Useful when only template files have been modified. With template dependency tracking active, only the pages whose template closure includes an edited template are re-rendered; otherwise (tracking off, a dynamic include in the graph, or templates added/removed) every page re-renders as before.
force_pages are rendered regardless of template impact — callers
that re-parsed content (run_incremental_then_rerender) pass them so
the selective path can't skip a content-changed page. Their taxonomy
membership may have changed too, so taxonomy pages regenerate
whenever force_pages are present.
membership_changed signals that pages left the built set in this
pass (a draft/expired/future flip): the SEO surfaces and taxonomy
pages must refresh even when no template content changed, and the
identical-templates early return must not skip that work.
listing_sets is the caller's pre-re-parse snapshot of the page-set
projections (see snapshot_listing_sets): the selective render set
must also take in the listing pages that print a projection the
content re-parse moved. Nil for a pure template edit, which changes
no page metadata.
The most recently built site (nil before the first build). The dev server's lazy OG handler reads it to match a requested image path back to the page that owns it.
Map source paths that were removed from disk to the output files
they produced in the last build. A rebuild rewrites surviving pages
but never deletes what's gone, so the serve watcher captures this
BEFORE rebuilding (while @site still knows the page's URL/slug) and
removes the orphans after — otherwise a deleted page keeps serving
200 and ships with the next deploy of public/.
path may arrive in any spelling — get_output_path hands back the
canonical absolute form while alias/pagination paths are built by
joining the output directory — so it is resolved against cwd (the
caller's, resolved once) before the lookup.
Did the static copy write anything at all this build? The gate in filter_changed_pages resolves paths against the working directory, and this lets it skip that work entirely on the common warm build where no static file changed.
Delete the alias stubs and /page/N/ files a page's previous render
wrote and its render in the pass that just finished did not, plus
every derived file of a page that is no longer in the site
(deleted, drafted, render = false). Called once a render pass is
complete, never per page: a stub one page dropped may be exactly
the file another page claims in the same pass, so nothing a live
page or any current stub still claims is deleted.
Take (and forget) what this page derived, remembering it as what the
page's render in this pass wrote (see @derived_outputs_this_pass).
Remember what this feed generation published and, on a serve pass
(prune), delete the feeds the previous generation published and
this one did not — a section whose generate_feeds was just turned
off. Full builds leave that to the Finalize claims diff.
Run one taxonomy generation pass, then delete the files the previous pass on this builder wrote and this one did not: a term whose last post was deleted, re-tagged or drafted, with its feed and pagination pages, or a whole taxonomy dropped from config.toml.
Paths are compared relative to output_dir, so a pass into a
different output directory matches nothing and deletes nothing. A
file the current site renders a page to is never deleted, and
neither is anything when the pass raised (the previous set is kept
and widened, so the next complete pass still sees it).
True when path was (re)written by this build.
Exact on every filesystem with sub-second mtimes (APFS, ext4, btrfs, NTFS, xfs): a previous build's file is stamped strictly before this build's epoch, and everything this build writes strictly after. The comparison carries NO slack on purpose — a slack wide enough to cover a coarse filesystem is also wide enough to protect the previous build's output, which would disable pruning outright for any two builds run seconds apart.
On a filesystem that truncates mtimes to whole seconds a shadowed generator file written in the epoch's own second can read as older and be pruned. That is self-healing: the path leaves the claim list with it, and the next build's generator finds the file missing and writes it again.
Widening the comparison (a slack, or truncating the epoch to its own second) trades that for a PERMANENT failure instead. A path the prune skips is not re-claimed by the build that skipped it, so it never appears in a later build's stale list either — the leftover stays published forever. Two builds run seconds apart are ordinary, so the widened form also protects the PREVIOUS build's output as a matter of course: both widenings were measured against cache_stale_outputs_spec and disable pruning outright (12+ of its 22 examples). Erring toward pruning is the recoverable direction.
The stamped copies (static files, bundle assets — File.utime with
the SOURCE mtime) deliberately read as old, and they are exactly the
ones a live claim already protects.