@fwdslsh/unify
v0.9.0
Published
A static site generator for HTML-native composition: includes, layouts, and slots in plain HTML — no expression language, no client runtime
Maintainers
Readme
unify
HTML-native composition — no expression language, no client runtime.
unify is a static site generator for people fluent in HTML and CSS who want nothing to do with JavaScript frameworks, templating languages, or build tooling. Write a header, nav, footer, or page layout once, in plain HTML, and unify renders it into every page. The output is exactly the HTML and CSS you wrote — unify adds no JavaScript of its own. It replaces copy-paste chrome and Apache SSI; it does not compete with Hugo, Eleventy, or Astro.
The entire authoring surface is five things, learnable in five minutes:
| You want to… | You write… |
|---|---|
| Reuse a fragment (nav, footer, badge) | <include src="/_includes/nav.html"></include> |
| Wrap pages in a layout | nothing — the nearest _layout.html applies; data-layout="/path.html" picks one, "none" opts out |
| Mark where page content lands, or let pages replace a named region | <main> for the default; <slot name="footer"> in the layout, slot="footer" on a page element |
| Keep a file or folder out of the built site | a leading underscore: _draft.html, _includes/ |
| Ship a bare snippet exactly as written (for <include>, embeds, fetch) | name it *.fragment.html |
If it cannot be expressed with these five, it does not belong in unify.
The specification set in
docs/is authoritative, and every normative rule in it is covered by a test that runs against the real CLI.
Install
Standalone binary (Linux and macOS, x86_64 and arm64 — nothing else to install):
curl -fsSL https://raw.githubusercontent.com/fwdslsh/unify/main/install.sh | bashInstalls to ~/.local/bin; the script accepts --global, --dir PATH, --version vX.Y.Z, --force, and --dry-run. Binaries are attached to GitHub releases. No Windows binary yet — use the install below.
Node or Bun:
npx @fwdslsh/unify build # no install at all
npm install -g @fwdslsh/unify
bun add -g @fwdslsh/unifyNode >= 22.12.0 or Bun >= 1.2.0. The same unify runs on both and produces byte-identical output; the installed script picks Node by default, and bun add -g runs it under Bun. Deno is not supported. Never heard of either? Use the binary above.
Quick start
unify init # scaffold a starter site into src/
unify dev # build, watch, serve, reload — http://localhost:3000
unify build # write the final site to dist/ — upload it anywhereunify init scaffolds a complete site into src/ — a layout, a nav include, HTML and Markdown pages, CSS, and a robots.txt — plus two files at the project root, outside src/ so they can never publish: AGENTS.md, guidance for whoever (or whatever) edits the site next, and DEPLOY.md, the deployment recipe. Five templates: default, basic, blog, docs, portfolio. Every scaffold passes unify build --dry-run --strict and unify audit --strict out of the box.
New here? The tutorial is Getting Started.
How it works
A layout is a complete HTML page — open it in a browser and its slot fallbacks are its own preview:
<!-- src/_layout.html -->
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>— My Site</title>
<link rel="stylesheet" href="/assets/style.css">
</head>
<body>
<include src="/_includes/nav.html"></include>
<main><slot></slot></main>
<footer><slot name="footer"><p>© My Site</p></slot></footer>
</body>
</html>A page is also a complete, ordinary HTML document. It never mentions the layout — the nearest _layout.html applies automatically:
<!-- src/index.html -->
<!doctype html>
<html>
<head><title>Home</title></head>
<body>
<main>
<h1>Home</h1>
<p>This lands in the layout's <main>.</p>
</main>
</body>
</html>The built page is the layout with its <main> filled by the page's content, and the page's title joined in front of the layout's: <title>Home — My Site</title>. To replace a named region, a page puts one standard attribute on an ordinary element — <p slot="footer">© My Site — <a href="mailto:[email protected]">email us</a></p> — and that element replaces the layout's <slot name="footer">, tag and all. The precedence rule in one sentence: named fills go to named slots, everything else to the bare slot, else into <main>.
Markdown pages compose identically, with frontmatter supplying the head. title, layout, class, lang, dir, and schema are the frontmatter keys with behavior; every other key becomes a <meta> tag, and draft, permalink, and slug are errors that name what unify does instead. Built pages contain no <slot> elements, no data-layout attributes, and no injected script — the one unify token that can survive into output is <meta name="schema">, on a page that asked for a generated JSON-LD block.
The full model is specified in the Product Specification; the exact algorithms — slots, head merge, root attributes, URL rewriting — are in the Conformance Specification.
Commands
unify [build] build the site (default command)
unify audit evaluate the site the build would publish — writes nothing
unify dev build, watch, serve, and reload — the inner loop
unify watch build + rebuild on change, no server
unify init [template] scaffold a starter siteunify --help lists every option — among them --pretty-urls, --base-url (which also generates sitemap.xml, and feed.xml once a page declares schema: Article/BlogPosting), --canonical auto, --catalog, --search-corpus, --dry-run, and --strict. The CLI Reference documents every command, option, and exit code — there are no others. An optional unify.yaml at the source root holds saved flags and nothing more.
Examples
examples/ holds eight complete sites, each building clean under unify build --dry-run --strict. Four were authored by agents given nothing but the sixty-line authoring rules and a client brief, and kept because they passed review — so they show what the rules actually lead someone to build; the other four are hand-maintained, including unify's own documentation site built by unify, another generator (Eleventy) adopted as source through --generate, and a blog whose browse, filter, and search UI is data-driven from --catalog/--search-corpus. Between them: pages generated from a data file, client-side filtering, a section with its own chrome, a page with no chrome for embedding, deploying under a subdirectory, htmx swapping .fragment.html panels, a Svelte component compiled to an ordinary asset, and client-side search over generated JSON. examples/README.md names the pattern in each.
Documentation
Using unify
- Getting Started — the tutorial.
- Authoring Rules — the complete authoring surface, in under sixty lines.
- CLI Reference — every command, option, and exit code.
- Integrations — the compile-to-asset pattern: Svelte, TypeScript, or anything with a compiler, without adopting a framework.
- Eleventy + htmx — the advanced stack: another generator produces data-driven pages, unify composes them, htmx enhances them. Optional layers, and when not to reach for them.
- Catalog and Search — a blog list, facets, and a search box built client-side from the two files
--catalog/--search-corpuswrite. - Docker Usage — running the CLI in a container.
- Examples — eight complete sites and the patterns they demonstrate.
How unify is defined
- Product Specification — what unify is, the composition model, the non-goals.
- Conformance Specification — the normative implementer reference: exact algorithms, the head-merge table, the closed problem/advisory catalogues.
- Testing Strategy — the testing contract and the release gates.
- CI/CD Workflows — what each job runs and what it is allowed to mean.
How the documentation was validated
- Ratification — the evidence: agents authoring from the sixty lines in isolation, what each round found, and what it changed.
- Ratification Protocol — the procedure, for running another round.
- Migration Plan — how the current tree came to be. History, not current work.
For Agents
Authoring or editing a site built with unify? Review docs/authoring-rules.md — the complete authoring surface in under sixty lines — before writing anything, and do not substitute conventions from other generators: unify has no props, no expressions, no draft:/permalink:/slug: keys, and layouts do not chain.
unify initdrops anAGENTS.mdat the project root (outsidesrc/, so it cannot publish) restating the most commonly guessed-wrong rules. If the project has one, read it — it restates the rules linked above, never a variant of them.- Finish by checking:
unify build --dry-run --strict, thenunify audit --strict. Exit0fromunify buildmeansdist/is the complete site; non-zero means nothing was published and the previous output is untouched — never report success on a non-zero exit. unify --helplists the complete CLI. There are no other commands or flags; the authoring rules live in docs/authoring-rules.md and the scaffoldedAGENTS.md, not in a CLI command.
Contributing to unify itself? Start with CLAUDE.md and CONTRIBUTING.md; the Conformance Specification is normative.
Guarantees
- Content you wrote is never silently dropped. Anything that would lose authored content is a
problem, and problems block publishing. The milder severity,advisory, never blocks (under--strictit counts). Diagnostics are plain language, located, and name a fix — no rule codes. - Publishing is transactional. Composition and every check — including a reference check of every internal URL — run into a temporary tree, and
dist/is replaced only on zero problems. Exit0meansdist/is the complete site;1means problems were found and the previous output is byte-for-byte untouched;2means invalid usage. unify build --dry-run --strictruns the whole build and every check while writing nothing — the one-line CI lint.
And the guarantees that run the other way — what unify will never do:
- Ship JavaScript. Your scripts pass through untouched; unify injects none of its own (dev's live reload never reaches
dist/). - Grow a templating language. No variables, loops, conditionals, or props. Derived pages come from a script you own, run before the build — a feed is the one exception (
schema:plus--base-url). - Become a component framework. Slots fill layouts;
<include>splices files verbatim. - Need configuration. Conventions, not config files.
- Scope your CSS, or be a real web server.
@scope/@layerand a real server already answer those.
The full list, with the reasoning and the accepted costs, is Product Specification §5.
Auditing
unify audit answers a different question — not is this build sound? but is this site complete? It runs the same pipeline, publishes nothing, and reports findings: a page with no description, two pages sharing a title, a #fragment link that names nothing, duplicate ids, a page nothing links to, structured data that contradicts its page. No score, no grade, no character counts — and build never runs any of it, so no finding can hold up a release. unify audit --strict is the opt-in CI gate, --format json|sarif is the machine-readable mirror, and --external checks off-origin links — the one flag in the product that touches the network. While unify dev is running, the same findings are a live page at http://localhost:3000/_unify/, grouped by page with each page's record beside them.
Contributing
See CONTRIBUTING.md. Development setup:
git clone https://github.com/fwdslsh/unify
cd unify
bun install
bun testLicense
Mozilla Public License 2.0. See LICENSE.
