#Crucible
Crucible is a static documentation generator that transforms Markdown into HTML through an XML intermediate representation and XSLT 4.0 transformation. It's built with the PhoenixmlDb XSLT engine — the same documentation site you're reading was built with Crucible.
#Installation
dotnet tool install -g Crucible.Cli#Quick Start
# Create a new documentation project
crucible init
# Build the site
crucible build
# View the result
open dist/index.htmlThat's it. crucible init creates a crucible.yaml config file and a starter docs/index.md. crucible build generates a complete static site in dist/.
#How It Works
Markdown (.md) → Parse → XML (intermediate) → XSLT Transform → HTML (static site)-
Parse: Markdown files with YAML frontmatter are parsed, transformed into an XML intermediate representation, and a site manifest is built from the directory structure.
-
Transform: XSLT stylesheets transform each XML document into HTML, generating navigation, SEO metadata, and a sitemap.
-
Output: A complete static site ready for deployment to Cloudflare Pages, GitHub Pages, Netlify, or any static hosting.
#Commands
#crucible init
Scaffolds a new documentation project in the current directory.
crucible init
crucible init --force # Overwrite existing crucible.yamlCreates:
-
crucible.yaml— site configuration with commented defaults -
docs/index.md— starter homepage with example frontmatter
#crucible build
Builds the documentation site.
crucible build [options]|
Option |
Short |
Description |
Default |
|---|---|---|---|
|
|
|
Source directory containing |
|
|
|
|
Output directory for generated site |
|
|
|
|
Custom theme directory |
built-in default |
|
|
Base URL for links and sitemap |
|
|
|
|
Site title |
from root |
|
|
|
Build stage: |
|
|
|
|
Delete output directory before building |
||
|
|
Include pages marked |
||
|
|
Treat warnings as errors |
||
|
|
|
Verbose output |
|
|
|
Show per-stage timing |
||
|
|
|
Show help |
#Exit Codes
|
Code |
Meaning |
|---|---|
|
0 |
Success |
|
1 |
Usage error (bad arguments, missing source directory) |
|
2 |
Parse error (invalid Markdown or YAML) |
|
3 |
Transform error (XSLT failure) |
#Configuration
#crucible.yaml
The optional configuration file. CLI flags override config values.
# Site metadata
title: My Documentation
base-url: /
# Directories
source: ./docs
output: ./dist
# Custom theme (uncomment to use)
# theme: ./my-theme
# Extensions
# extensions:
# - Crucible.Extensions.MermaidAll fields have sensible defaults. The config file is never required — you can run crucible build with just a docs/ directory.
#Writing Documentation
#Directory Structure
Your documentation is a directory of Markdown files. The directory structure becomes the site navigation:
docs/
├── index.md → Homepage
├── getting-started/
│ ├── index.md → Section landing page
│ ├── installation.md → Getting Started > Installation
│ └── quick-start.md → Getting Started > Quick Start
├── guides/
│ ├── authentication.md → Guides > Authentication
│ └── deployment.md → Guides > Deployment
└── reference/
└── api.md → Reference > API-
Files become pages
-
Directories become navigation sections
-
index.mdin a directory provides the section title (if present) -
Directories without
index.mduse the directory name (title-cased) as the section title
#YAML Frontmatter
Every Markdown file starts with YAML frontmatter:
---
title: Installation
description: How to install the project
sort: 1
updated: 2026-03-15
tags:
- getting-started
- setup
---
# Installation
Your content here...|
Field |
Type |
Required |
Description |
|---|---|---|---|
|
|
string |
yes |
Page title (used in navigation, |
|
|
string |
no |
Page description (used in |
|
|
integer |
no |
Sort order within the section (sorted pages come first, then alphabetical) |
|
|
date |
no |
Last updated date (used in sitemap |
|
|
string[] |
no |
Tags for categorization |
|
|
bool |
no |
If |
|
|
string |
no |
Override XSLT template for this page |
#Markdown Features
Crucible supports standard Markdown with these extensions:
Fenced code blocks with language syntax:
```csharp
var x = 42;
```Tables (GFM pipe tables):
| Column A | Column B |
|----------|----------|
| Value 1 | Value 2 |Admonitions using custom container syntax:
::: note
This is a note admonition.
:::
::: warning
Be careful with this setting.
:::
::: tip
Here's a helpful tip.
:::Supported types: note, warning, tip, important, caution.
Mermaid diagrams (rendered client-side):
```mermaid
graph LR; A-->B; B-->C;
```#Internal Links
Link between documentation pages using relative or root-relative paths:
<!-- Relative link (from current file's location) -->
See [Installation](../getting-started/installation.md)
<!-- Root-relative link (from docs/ root) -->
See [Installation](/getting-started/installation.md)Crucible automatically rewrites .md links to .html in the output and warns about broken links.
#Features
#Search
Crucible generates a client-side search index during build. Search is powered by Lunr.js — no server required. The search box appears in the sidebar and provides instant fuzzy search across all page titles, descriptions, headings, and content.
#SEO
Every generated page includes:
-
<title>— page title + site title -
<meta name="description">— from frontmatter -
<link rel="canonical">— from base URL + page path -
Open Graph tags (
og:title,og:description,og:url,og:type) -
sitemap.xmlwith<lastmod>dates
#Dark Mode
The default theme includes automatic dark mode that respects your system preference. A toggle button in the header lets you switch manually, and your preference is saved across pages.
#Navigation
Navigation is automatically generated from the directory structure:
-
Sidebar with nested sections
-
Active page highlighting
-
Collapsible sections on mobile
#Responsive Design
The default theme is fully responsive. The sidebar collapses into a hamburger menu on mobile devices.
#Staged Builds
For debugging or advanced workflows, you can run individual stages:
# Stage 1: Parse Markdown → XML intermediate
crucible build --stage ParseOnly -o ./intermediate
# Inspect the XML output
cat intermediate/index.xml
cat intermediate/site-manifest.xml
# Stage 2: Transform XML → HTML
crucible build --stage TransformOnly -s ./intermediate -o ./distThis is useful for:
-
Inspecting the XML intermediate representation
-
Debugging XSLT template issues
-
Custom post-processing between stages
#Custom Themes
Create a directory with XSLT stylesheets and static assets:
my-theme/
├── page.xslt # Main page transform
├── navigation.xslt # Navigation component (optional)
├── sitemap.xslt # Sitemap generator
├── css/
│ └── style.css
└── js/
└── theme.jsUse with:
crucible build --theme ./my-themeOr in crucible.yaml:
theme: ./my-themeThe page.xslt receives these parameters:
-
$site-manifest-uri— path to the site manifest XML -
$base-url— base URL prefix -
$site-title— site title -
$current-path— current page path (for navigation highlighting)
The input document follows the Crucible XML schema — see the design spec for element details.
#Extensions
Crucible supports extensions that hook into the Markdown-to-XML pipeline. The built-in Mermaid extension is an example — it intercepts fenced code blocks with mermaid language and emits a <mermaid> XML element instead of a <code-block>.
#Plugin Directory
Place extension DLLs in a plugins/ directory next to your crucible.yaml. Crucible loads them automatically.
#Writing Extensions
Extensions implement ICrucibleExtension from the Crucible.Core NuGet package:
using Crucible.Core.Extensions;
using Markdig.Syntax;
public class MyExtension : ICrucibleExtension
{
public string Name => "My Extension";
public bool CanProcess(Type markdigNodeType)
=> markdigNodeType == typeof(FencedCodeBlock);
public bool ProcessNode(MarkdownObject node, XmlEmitterContext context)
{
// Write custom XML elements to context.Writer
// Return true if you handled the node, false to use default processing
return false;
}
public IEnumerable<CrucibleAsset> GetAssets()
{
// Return JS/CSS assets to include in the output
yield break;
}
}#Deployment
#Cloudflare Pages
# Build command
crucible build --base-url https://docs.example.com
# Output directory: dist#GitHub Pages
# .github/workflows/deploy.yml
name: Deploy Docs
on:
push:
branches: [main]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-dotnet@v4
with:
dotnet-version: '10.0.x'
- run: dotnet tool install -g Crucible.Cli
- run: crucible build --base-url /repo-name/
- uses: actions/upload-pages-artifact@v3
with:
path: dist
deploy:
needs: build
permissions:
pages: write
id-token: write
environment:
name: github-pages
runs-on: ubuntu-latest
steps:
- uses: actions/deploy-pages@v4#Netlify
# netlify.toml
[build]
command = "dotnet tool install -g Crucible.Cli && crucible build"
publish = "dist"#Any Static Host
Crucible generates a self-contained dist/ directory. Upload it anywhere that serves static files.