trellis_site 0.11.1
trellis_site: ^0.11.1 copied to clipboard
Static site generator for Trellis — Markdown content, Trellis templates, Hugo-inspired conventions.
Changelog #
0.11.1 #
Changed #
- Lattice theme: new
show_site_demoparam (defaultfalse) renders a Markdown-to-page block on the home page from asite_demoblock indata/lattice.yaml; dropped whole when a required key is missing, like the other home sections. The Trellis docs site enables it, gains a top-level Sites section, and shipsexamples/docs_site/.
Fixed #
- README install snippet pinned
trellis_site: ^0.1.0, a range no published version of the package matches, so a copied snippet faileddart pub get. It now tracks the lockstep version andtool/version_lockstep.shkeeps it current.
0.11.0 #
Breaking #
- Symlinks under
static/are no longer published. Both static-asset walks – the site'sstatic/and an installed theme's – now list withfollowLinks: false, so a symlinked file or directory is skipped instead of copied. This closes a containment hole: astatic/secrets -> ~/.sshlink, committed in a site or in the third-party code of a theme installed withtrellis theme add, published its target intooutput/with no warning and a zero exit. It also drops symlinks that were there on purpose – astatic/link into a shared asset directory stops appearing in the built site andBuildResult.staticFileCountfalls to match, with no warning. Put the real files understatic/, or copy them in beforetrellis build. - Symlinks under
content/are no longer rendered. Content discovery also lists withfollowLinks: false, so a symlinked Markdown or HTML file cannot publish a readable file outside the site. The build emits a warning for each skipped content symlink. Replace intentional links with real content before building.
Added #
PageGenerator.emittedPagesexposes the absolute HTML paths written by the most recentgenerateAllpass, including paginated outputs and excluding files merely copied fromstatic/. The list resets at the start of each pass.BuildResult.themeShadowedLayoutsalways lists the active theme layouts shadowed by site layouts, whether or not the resulting theme is inert.BuildResult.themeIsInertseparately reports that the theme's stylesheets and scripts were published but no emitted page linked any of them. The inertness check covers the theme'sstatic/.css/.js, generatedcss/theme-props.css, and compiledsass/output; images and fonts are excluded, since a page can carry a theme favicon and still be unstyled. A site may therefore report shadowed layouts whilethemeIsInertremains false when its rendered shell still links a theme asset.shadowedThemeLayouts({siteDir, siteLayoutsDir, themeDir}), exported frompackage:trellis_site/trellis_site.dart, returns the layouts a theme provides that the site also provides at the same relative path, as site-root-relative paths. Those are the layouts site-first resolution makes unreachable; it is what populatesBuildResult.themeShadowedLayoutsand whattrellis theme addreports at install time.resolveFrontMatterDate(value)– the front-matterdate:resolver thatFeedGeneratorandSitemapGeneratornow share, exported frompackage:trellis_site/trellis_site.dart. Returns a UTCDateTime, ornullwhen the value is absent or unparseable so callers fall back to the source file's mtime.
Changed #
baseUrlis normalized to drop its trailing slash when the config loads (new staticSiteConfig.normalizeBaseUrl).baseUrl: https://example.com/previously reached templates verbatim, and${page.url}is always root-absolute, so the${site.baseUrl} + ${page.url}themes use forog:urlemitted a doubled slash –https://example.com//on the home page.${site.baseUrl}now renders ashttps://example.com, andhttps://example.com/base/ashttps://example.com/base; abaseUrlalready written without the slash is unchanged. A value made only of slashes is left alone, because emptying it would silently switch off sitemap and feed generation, which both gate on a non-empty base.sitemap.xmland the feeds carry their own trailing-slash trim and are unaffected. A template that joins a relative path onto${site.baseUrl}and relied on the configured trailing slash to separate them now has to write the slash itself.- Zone-less front matter dates are resolved reproducibly in UTC (TD-014).
date: 2026-01-01previously parsed as local midnight, sofeed.xml<updated>andrss.xml<pubDate>depended on the build machine's timezone – the same content emitted2025-12-31T23:00:00Zon a CET laptop and2026-01-01T00:00:00Zon a UTC runner. Feed timestamps are now identical on every machine. A zone-less date-time such as2026-03-15T09:30:00likewise names UTC calendar fields instead of the build machine's local time; an explicitZor numeric offset remains authoritative. Sites that relied on local-time interpretation should add their intended offset.sitemap.xml<lastmod>uses the same resolver and is timezone-stable for date-only, zone-less, and explicit-offset values. - A
theme:that resolves to no installed theme now names the command that installs it. TheThemeManifestExceptionreadsTheme 'x' not found in themes/ — install it first, e.g. 'trellis theme add <url-or-path> --theme x'instead of stopping at the directory that is missing.
Documentation #
- The README's built-in Verdant install now uses
trellis theme add https://github.com/tolo/trelliswith--theme verdant --ref v0.11.0. It previously pointed at a per-theme repository (tolo/trellis-theme-verdant) that does not exist – the themes live underthemes/in the one repository.
Notes #
- The Trellis repository gained three themes in this release – Lattice (documentation), Folio (reference) and
Meadow (product landing), joining Arbor, Bloom and Verdant. They are not part of this package and do not
travel through pub.dev: themes live under
themes/intolo/trellisand are installed into a site withtrellis theme add https://github.com/tolo/trelliswith--theme <name> --ref v0.11.0. What this package provides is the machinery that discovers, loads, merges the params of and renders them.
0.10.2 #
Changed #
- Lockstep version bump to keep all Trellis SDK packages on a single shared version. No functional changes in this package.
0.10.1 #
Fixed #
- Build time no longer grows quadratically with the number of pages in a single section.
${page.prev}/${page.next}resolution re-sorted the whole section for every page it rendered, so a flat blog with thousands of posts in one section slowed sharply (a 4000-post section spent ~8 s on neighbour resolution alone; 2000 posts ~1.9 s). The section ordering is now computed once per build pass. A full build of a 2000-post flat blog now spends ~350 ms in total page generation. Sites with pages spread across many sections were barely affected and see no change.
Added #
benchmark/site_scale_benchmark.dart– a scale benchmark for the page-generation hot paths, run by hand when touchingorderedSectionPages, prev/next resolution, orNavigationBuilder. Reports growth ratios across site sizes so superlinear behaviour is visible, includes the flat single-section worst case, and times a fullgenerateAllon that worst case so the memoized path is measured where it lives.
0.10.0 #
Added #
- Per-page taxonomy term links (
${page.termLinks.<taxonomy>}): every content page now exposes its own front-matter terms, per declared taxonomy, as a list of{name, slug, url, count}maps whoseurlis the canonical slugified term-page path (/tags/hello-world/) resolved from the same index that generates the term pages. Themes link tags via${tag.url}instead of string-building/{taxonomy}/{rawTerm}/. The key is present only when the taxonomy is declared and the page has terms, so templates not reading it are byte-for-byte unchanged;${taxonomy.<name>}remains the site-global term list. - Build-time syntax highlighting (ADR-010): fenced Markdown code blocks are highlighted at build time by a new
CodeHighlighter(backed bypackage:highlight), which emits highlight.js-style.hljs-*token spans directly into the built HTML — no client-side highlighter JS is shipped or fetched. Controlled by a new top-levelhighlight:key intrellis_site.yaml, on by default; setenabled: falseunder it to fall back to plain<pre><code>. Unknown or unspecified languages are left unhighlighted.
Changed #
- Syntax-highlighting token CSS migrated from Prism
.token.*to.hljs-*(ADR-010) across the official themes, to match the new build-time output. The per-themesyntax_highlightingparam is retired — highlighting is now controlled site-side via thehighlight:config key, not per theme. Migration: replacetheme_params: { syntax_highlighting: false }withhighlight: { enabled: false }intrellis_site.yaml; the old param is now ignored with anUnknown theme paramwarning.
Fixed #
- Theme SASS bridge hardening (follow-up review of the 0.9.1 fix): string params are now emitted via SASS interpolation (
#{"..."}) instead of the deprecated globalunquote()(removed in Dart Sass 3.0.0), eliminating theglobal-builtindeprecation warnings 0.9.1 introduced. A SASS interpolation marker (#{...}) in a param value is neutralized to literal text — never evaluated — across every emit path: string values, the hex-color fast-path (now gated on a real hex pattern rather than string length, so an interpolation-shaped value like#{9}can no longer slip through raw and be evaluated),color-typed values, and map keys. Multiline (newline-containing) param values no longer abort the SASS compile (escaped as CSS\a). (Structural characters;{}in a value remain a separate, tracked robustness limitation — see TD-011.) - Verdant
_default/single.htmltag pills broke the build for any tagged post. The pills built each tag URL as@{/tags/{tag}/}— invalid Trellis URL-expression syntax (@{...}supports query params, not{path}templating) — so rendering a post withshow_tagson threw anExpressionExceptionand aborted the build. Even had it parsed, interpolating the raw tag produced/tags/Hello World/while the term page lives at the slugified/tags/hello-world/— a 404 for any tag with uppercase, spaces, or punctuation. Pills now iterate${page.termLinks.tags}and link via each term's canonicalurl, matching the generated term page exactly. The bundledexample/site now declarestaxonomies: [tags]so its term pages are generated and the pills resolve. - Verdant
example/site was not buildable standalone. Two problems, both now fixed to match the workingarborexample: (1)theme: ..never resolved — theme resolution joins<siteDir>/themes/<value>, so..pointed at the example dir, not the theme; the example now ships a git-trackedthemes/verdant → ../..symlink and setstheme: verdant. (2) The example'shome.htmloverride wrapped an already-complete page URL in a@{...}URL expression (@{${p.url}}), a parse error; it now links via${p.url}like the theme's own layout.trellis buildonthemes/verdant/exampleis green (8 pages, 0 broken links).
0.9.1 #
Fixed #
- Theme SASS bridge: string params compiled into invalid quoted CSS values. String-typed theme params (
font_family,max_width, ...) were written to the generated_theme_params.scssas quoted SASS strings; the bridge loads before the theme's_variables.scss, so the quoted value won and compiled into invalid CSS (font-family: "system-ui, ..."— ignored by browsers, falling back to serif;max-width: "1200px"— dropped, unconstraining the layout). Affected every theme built with a params bridge (arbor and verdant alike). The generator now emits values viaunquote()with escaping for embedded quotes/backslashes.
0.9.0 #
Added #
- URL path-prefix (
pathPrefix): serve a site under a sub-path (e.g. GitHub project pages at/my-site/) via a newpathPrefixconfig key. When set, every engine-derived internal URL -- page links (${page.url}), the section/menu tree, prev/next, and search-indexurlfields -- resolves under the prefix, andsitemap.xml/feeds composebaseUrlwith the prefixed URL so the prefix appears exactly once. The value normalizes leading/trailing slashes to a canonical/x/form (my-site,/my-site,my-site/,/my-site/all →/my-site/);''and/mean root-served. Absolute/external URLs are left untouched. An un-normalizable value (scheme-bearing/protocol-relative URL, non-string, whitespace or:anywhere, a./..path segment, or an empty interior segment such asa//b) aborts the build with aSiteConfigExceptionand writes no output. With nopathPrefix(the default) output is byte-for-byte unchanged. The normalized prefix is exposed to templates as${site.pathPrefix}so theme authors can prefix hand-written literal asset references, e.g.tl:href="${site.pathPrefix} + 'css/main.css'". The prefix lives in emitted URLs only, not in the on-disk output layout — files are written at their unprefixed paths so the wholeoutput/tree can be published as the site root and mounted under the prefix by the host (e.g. GitHub Project Pages). Hand-written root-absolute internal links and asset references in the emitted HTML (Markdown content links, theme literals,<script src>/<link href>) are automatically rewritten to carry the prefix (expected SSG behavior); relative links, in-page anchors, external URLs, escaped code examples, and already-prefixed engine URLs are left untouched. - Weighted page ordering: pages may declare an integer
weightfront-matter field; within a section, weighted pages sort first by ascendingweight, then ties and all unweighted pages fall through to the existing date-descending-then-URL order. Sections with noweightare byte-for-byte unchanged. A malformedweight(non-integer) is ignored with a build warning naming the page and treated as unweighted; the build never aborts. - Nested-section lineage: new additive
Page.sectionPathfield exposes a page's full nested section lineage (e.g.docs/guides), available in templates as${page.sectionPath}(string) and${page.ancestors}(cumulative list).${page.section}is unchanged and remains the top-level folder. - A nested
_index.mdsection listing now contains exactly its own-level pages and excludes sibling sub-sections' pages. orderedSectionPages(sectionPath, allPages)-- a reusable engine function returning a section's own-level content pages in canonical (weight-aware) order. The${pages}listing, pagination, and downstream ordering consumers route through this single function.- Hierarchical navigation menu (
${site.menu}): a nested navigation tree mirroring the content section hierarchy, available to every page render (single, section, home, and taxonomy virtual pages -- not only list pages). Each node is a{title, url, children}map; own-level pages within a section are ordered viaorderedSectionPages(same canonical weight-aware order as${pages}), and sections nest byPage.sectionPathlineage. The tree carries no per-page active flag -- it is built once and shared across renders, so a theme marks the active page and its ancestor trail at render time by comparing eachnode.urlagainst${page.url}. A node'stitlefollows a 3-tier precedence:menu_titlefront matter →titlefront matter → humanized URL slug (getting-started→Getting Started). Sibling section nodes are ordered by their section page's (_index.md)weight(ascending, weighted before unweighted), then lexically — so a docs IA can order its sections deliberately; with no section weights the order is the previous pure-lexical order (backward-compatible). Draft pages and pages withmenu_exclude: trueare excluded (a section that opts out drops its own node but hoists surviving children to its parent; a folder with no_index.mdstill yields a synthesized node titled from the humanized folder name). Empty or fully-excluded content yields an empty list (never null). Sites whose templates do not reference${site.menu}are byte-for-byte unchanged. - Front-matter keys
menu_title(string) andmenu_exclude(bool): control a page's${site.menu}node title and inclusion. - In-section prev/next (
${page.prev}/${page.next}): every single doc page exposes references to its immediate neighbors within its own section, each a{url, title}map, so a doc reads straight through in its authored order. Neighbors follow the section's canonical (weight-aware) order viaorderedSectionPages-- the same ordering as${pages}and${site.menu}, with no separate prev/next sort (weighted sections read inweightorder, not date order). Boundaries are handled by absence: the first page has noprev, the last has nonext, and a single-page section has neither, so atl:if-guarded region renders only the links that exist. Only single doc pages receive these keys; section, home, and taxonomy pages do not (list-page sequencing stays with${pagination.*}). Neighbortitleuses the same 3-tier fallback as${site.menu}(menu_title→title→ humanized URL segment), so a titleless neighbor still renders a readable label. Sites whose templates do not reference${page.prev}/${page.next}are byte-for-byte unchanged. - Structured breadcrumbs (
${page.breadcrumbs}): every page exposes a structured breadcrumb trail -- one{url, title}node per ancestor section, shallow-to-deep (mirroring${page.ancestors}). Titles use the${site.menu}3-tier fallback (menu_title→title→ humanized folder segment) so labels read like the menu (Guides, notdocs/guides); URLs are the section's own path-prefix-aware URL (empty for a folder with no_index.md). Additive to the unchanged${page.ancestors}cumulative-path list.
Fixed #
- Relative
theme:paths now resolve. Atheme:value that escapes the site directory (e.g.theme: ../../themes/arbor, used when a site references a repo-root theme) is now normalized so both the manifest loader and the templateFileSystemLoaderresolve a real path. Previously the un-collapsed..segments (through a non-existent<siteDir>/themesdirectory) made the template loader throw "base path does not exist". Bare theme names (theme: verdant) are unaffected.
0.8.2 #
Changed #
- Lockstep version bump to keep all Trellis SDK packages on a single shared version. No functional changes in this package.
0.8.1 #
Changed #
- Lockstep version bump to keep all Trellis SDK packages on a single shared version. No functional changes in this package.
0.8.0 #
Changed #
- Version aligned to the unified Trellis SDK lockstep versioning scheme — all SDK packages now share a single version number and are released together. No functional changes since 0.2.0.
0.2.0 #
Added #
- Theme system:
ThemeManifest,ThemeConfig, andThemeParamMergerfor parsingtheme.yamlmanifests and deep-merging theme params with site overrides. ThemeAwareLoaderfor site-first, theme-fallback layout resolution withtheme:prefix support for cross-boundarytl:extends.ThemeSassGeneratorfor auto-generating_theme_params.scss(SASS variables) and_theme_custom_props.css(CSS custom properties) from merged theme params.ThemeBuildConfigandSkinModefor SASS bridge configuration and skin file resolution (light, dark, auto).- Theme static asset merging (theme
static/copied to output, sitestatic/wins on conflict). - Theme data file merging (theme
data/as fallback, sitedata/wins per-file). ${theme.*}template context namespace for accessing merged theme params.- Build warnings for unknown
theme_params:keys. siteVersionconstant for runtime version identification.
Changed #
SiteConfigextended withthemeConfigfield (parsed fromtheme:,theme_ref:,theme_params:intrellis_site.yaml).BuildResultextended with optionalThemeBuildConfigfor CLI SASS compilation.PageGeneratoraccepts externalTemplateLoader,layoutSearchPaths, andthemeDataDirfor theme-aware builds.- SASS load path order:
.trellis/build/(bridge) →site/sass/→theme/sass/.
0.1.0 #
Added #
ContentDiscoveryfor recursive Markdown scanning, page-bundle support, and Hugo-style URL derivation.FrontMatterParserfor YAML front matter extraction and validation.MarkdownRendererfor GitHub-flavored Markdown, summary extraction, and table-of-contents generation.PageGeneratorfor layout resolution, data cascade assembly, and page rendering.TrellisSitefor full build orchestration across content, layouts, data, static assets, and output.BuildResultandBuildWarningfor build reporting.SiteConfigfortrellis_site.yamlloading with configurable content, layouts, static, data, and output directories.TaxonomyCollectorandPaginatorfor section, taxonomy, and list-page generation.ShortcodeProcessorfor pre-Markdown and post-Markdown shortcodes rendered through Trellis templates.
Generated Output #
SitemapGeneratorforsitemap.xml.FeedGenerator,FeedConfig, andFeedResultfor Atom, RSS, and per-section feeds.SearchIndexGeneratorandSearchConfigfor JSON search indexes compatible with client-side search tools.
Content Features #
- Global data file loading from
data/*.yaml. - Draft filtering, page bundle asset copying, and site-level params exposed to templates.