# Contributing to SSG
Welcome! We're thrilled that you're interested in contributing to SSG. Whether you're looking to report a bug, suggest a feature, or submit code, this guide will help you get started.
## Development setup
### Prerequisites
- [Rust](https://rustup.rs/) 1.88.0 or later
- Git with commit signing configured (see below)
### Getting started
```bash
git clone https://github.com/sebastienrousseau/static-site-generator.git
cd static-site-generator
cargo build
cargo test
```
### Useful commands
```bash
make init # Bootstrap (rustfmt + clippy + cargo-deny + hooks + build)
make build # Build the project
make test # Run all tests
make bench # Run Criterion benchmarks
make lint # Lint with Clippy
make format # Format code with rustfmt
make deny # Check dependencies for security/license issues
make doc # Generate API docs and open in browser
make a11y # Run accessibility audit on example site
make clean # Remove build artifacts
```
## Signed commits
All **commits and tags** must be signed. We accept SSH (recommended) or GPG signatures. Unsigned PRs are blocked by branch protection on `main`.
### One-time setup
```bash
# SSH signing — works on macOS, Linux, WSL and Windows (Git for Windows)
git config --global gpg.format ssh
git config --global user.signingkey ~/.ssh/id_ed25519.pub
git config --global commit.gpgsign true
git config --global tag.gpgsign true
```
Then register the same key on GitHub: **Settings → SSH and GPG keys → New SSH key → Key type: Signing Key**.
Prefer GPG?
```bash
git config --global commit.gpgsign true
git config --global tag.gpgsign true
git config --global user.signingkey YOUR_GPG_KEY_ID
```
### Per-repo defaults
```bash
cd static-site-generator
git config commit.gpgsign true
git config tag.gpgsign true
```
### Verify
```bash
git commit -S --allow-empty -m "chore: signature smoke test"
git log --show-signature -1
# Expect: Good "git" signature for <your email>
```
> **Tip:** with `commit.gpgsign = true` set globally you never need to remember the `-S` flag.
## Architecture
```
src/
lib.rs — Orchestrator: run() → plugin pipeline → compile → serve
lib.rs — Orchestrator: run() → plugin pipeline → compile → serve
main.rs — Binary entry point (delegates to lib::run)
cmd.rs — CLI parsing, SsgConfig, input validation
process.rs — Argument-driven site processing + directory creation
plugin.rs — Plugin trait + PluginManager
plugins.rs — Built-in MinifyPlugin, ImageOptiPlugin, DeployPlugin
frontmatter.rs — Frontmatter extraction + .meta.json sidecars
tera_engine.rs — Tera template engine wrapper
tera_plugin.rs — Tera rendering plugin
seo.rs — SeoPlugin, JsonLdPlugin, CanonicalPlugin, RobotsPlugin
ai.rs — AI readiness (llms.txt, meta tags, alt validation)
accessibility.rs — WCAG checker + ARIA validation
search.rs — Client-side search index + localized SearchLabels
highlight.rs — Syntax highlighting for code blocks
shortcodes.rs — Shortcode expansion (youtube, gist, figure, admonitions)
markdown_ext.rs — GFM extensions (tables, strikethrough, task lists)
image_plugin.rs — Image optimization (WebP, responsive srcset)
assets.rs — Asset fingerprinting + SRI hashes
deploy.rs — Deployment adapters (Netlify, Vercel, Cloudflare, GitHub Pages)
scaffold.rs — Project scaffolding (ssg --new)
schema.rs — JSON Schema generator for configuration
cache.rs — Incremental build cache
stream.rs — High-performance streaming I/O
walk.rs — Shared bounded directory walkers
watch.rs — Polling-based file watcher for live rebuild
livereload.rs — WebSocket live-reload injection
pagination.rs — Pagination plugin for listing pages
taxonomy.rs — Taxonomy generation (tags, categories)
drafts.rs — Draft content filtering plugin
```
### Plugin lifecycle
Every build runs the plugin pipeline in this order:
```mermaid
sequenceDiagram
autonumber
participant CLI as ssg::run()
participant PM as PluginManager
participant P as Plugin (×N)
participant SDG as staticdatagen::compile
participant SRV as serve_site
CLI->>PM: run_before_compile(&ctx)
PM->>P: before_compile(&ctx)
Note right of P: TeraPlugin emits .meta.json sidecars<br/>MarkdownExtPlugin pre-processes GFM<br/>DraftPlugin filters draft pages
CLI->>SDG: compile(build, content, site, template)
SDG-->>CLI: Vec<FileData>
CLI->>PM: run_after_compile(&ctx)
PM->>P: after_compile(&ctx)
Note right of P: SeoPlugin · JsonLdPlugin · CanonicalPlugin<br/>RobotsPlugin · MinifyPlugin · SearchPlugin<br/>AccessibilityPlugin · HighlightPlugin
CLI->>PM: run_on_serve(&ctx)
PM->>P: on_serve(&ctx)
Note right of P: LiveReloadPlugin injects WS script
CLI->>SRV: serve_site(site_dir)
```
Implement whichever hooks your plugin needs — the others default to no-ops.
### Writing a Plugin
```rust
use ssg::plugin::{Plugin, PluginContext};
use anyhow::Result;
#[derive(Debug)]
struct MyPlugin;
impl Plugin for MyPlugin {
fn name(&self) -> &str { "my-plugin" }
fn after_compile(&self, ctx: &PluginContext) -> Result<()> {
if let Some(config) = &ctx.config {
println!("Site: {}", config.site_name);
}
Ok(())
}
}
```
## How to contribute
### Reporting bugs
- Open an [issue](https://github.com/sebastienrousseau/static-site-generator/issues/new) with a descriptive title.
- Include steps to reproduce, expected vs actual behavior, and your OS/Rust version.
### Suggesting features
- Open an [issue](https://github.com/sebastienrousseau/static-site-generator/issues/new) describing the use case and proposed solution.
### Submitting code
1. Fork the repository.
2. Create a feature branch: `git checkout -b feat/my-feature`
3. Make your changes in `src/`. Add tests for new functionality.
4. Ensure all checks pass:
```bash
cargo fmt --all -- --check
cargo clippy --all-targets
cargo test
```
5. Commit with a signed, [conventional commit](https://www.conventionalcommits.org/) message:
```bash
git commit -S -m "feat: add support for TOML frontmatter"
```
6. Push and open a pull request against `main`.
### Pull request guidelines
- Keep PRs focused on a single change.
- Include a clear description of what changed and why.
- Ensure CI passes before requesting review.
- Reference related issues (e.g., `Closes #123`).
## Testing standards
SSG enforces enterprise-grade quality gates. All PRs must pass:
### Coverage floors (CI-enforced)
| Regions | 95% |
| Lines | 95% |
| Functions | 95% |
Coverage is measured with `cargo llvm-cov` and enforced in CI. If a PR
drops coverage below the floor, the build fails.
### Property-based testing
The project uses [proptest](https://crates.io/crates/proptest) for
generative testing. Property tests generate 1,000+ random inputs per
property and verify invariants like "never panics" and "output is valid."
Add property tests when modifying:
- Frontmatter parsing (`src/frontmatter.rs`)
- Shortcode expansion (`src/shortcodes.rs`)
- Markdown rendering (`src/markdown_ext.rs`)
- Slug generation (`src/taxonomy.rs`)
- Cache hashing (`src/stream.rs`)
### Performance regression gates
`tests/perf_regression.rs` contains timed assertions:
| 100-page compilation | < 5s |
| 50-page search index | < 500ms |
| 1000-file cache fingerprint | < 500ms |
| 10K depgraph invalidation | < 200ms |
If a change makes an operation slower than its budget, CI fails.
### Enterprise regression suite
`tests/perf_regression.rs` also validates:
- **Cache resilience**: corruption recovery, determinism, deleted files
- **Licence compliance**: SPDX headers on all `.rs` files, correct identifier
- **Localisation**: Unicode/RTL/CJK slug generation, BCP 47 codes
- **Pipeline integrity**: fused transform idempotency, plugin order
### Running tests locally
```bash
cargo test --workspace # All tests
cargo test --test perf_regression # Performance + enterprise gates
cargo test --test example_outputs # Example validation (serial, slow)
```
## Code of Conduct
Please read our [Code of Conduct](.github/CODE-OF-CONDUCT.md) before participating.
## License
By contributing, you agree that your contributions will be licensed under the same dual license as the project (MIT OR Apache-2.0).