# Contributing

> How to set up htmlpress locally, propose changes, and ship a release.

Htmlpress is an open-source project and we'd love your help, whether that's fixing a bug, polishing the docs, or proposing a new feature. This guide walks through everything you need to get a change from your editor into a release.

## Quick start

You'll need [Node.js](https://nodejs.org/) 24+, [pnpm](https://pnpm.io/) 11+ and [rustup](https://rustup.rs/); for building the Python wheel, [uv](https://docs.astral.sh/uv/). Then:

```
git clone https://github.com/datisthq/htmlpress.git
cd htmlpress
pnpm install
pnpm start
```

`pnpm install` also fetches the Rust crates when `cargo` is on your `PATH`. `pnpm start` runs htmlpress from source over this site's pages in `rust/docs/`, with live reload, so you can iterate on htmlpress while seeing the result in your browser.

## Repository layout

```
htmlpress/
├── rust/                # the binary and the crates.io package `htmlpress`
│   ├── src/
│   │   ├── actions/     #   asset/escape + list, config/load, page/read + render, site/…
│   │   ├── commands/    #   the `build`, `start`, `preview`, `clear` and `escape` commands
│   │   ├── models/      #   Asset, Config, Page, Site, BuildOptions, Error
│   │   ├── components/  #   built-in components, compiled in, like styles/ and scripts/
│   │   ├── program.rs   #   the CLI
│   │   └── lib.rs       #   the library façade
│   ├── tests/           #   CLI integration tests
│   └── docs/            #   this site, HTML built by htmlpress itself
├── typescript/          # the npm package `gethtmlpress`: a shim that runs the binary
├── python/              # the PyPI package `htmlpress`: maturin binary wheels
├── .skills/             # shipped agent skills for people who use htmlpress
├── .agents/skills/      # repo skills for coding agents (see AGENTS.md)
├── .scripts/            # repo-wide guards and the `pnpm squash` script
└── htmlpress.yaml       # this site's htmlpress config
```

All behaviour lives in `rust/`. The npm and PyPI packages only deliver the same binary, so a feature never lands in a wrapper.

## Local commands

Run these at the repo root; each chains into the sections with `pnpm -C <section>`.

| Command            | What it does                                                     |
| ------------------ | ---------------------------------------------------------------- |
| `pnpm start`       | Serve the docs site from source with live reload                 |
| `pnpm build:docs`  | Production build of the docs site                                |
| `pnpm build`       | Build the release binary, the npm shim and the Python wheel      |
| `pnpm check:types` | Type-check via `tsgo --noEmit` and `cargo check`                 |
| `pnpm check:lint`  | Format check + lint (`vp fmt`, `vp lint`, `cargo fmt`, `clippy`) |
| `pnpm check`       | Lint + type                                                      |
| `pnpm format`      | Auto-fix formatting (`vp fmt`, `cargo fmt`)                      |
| `pnpm test:unit`   | Run the repo guards, the shim's tests and `cargo test`           |
| `pnpm test`        | Lint + type + unit                                               |
| `pnpm scan`        | Secret and vulnerability scans (gitleaks, semgrep)               |

A change is ready to push when `pnpm test` is green and `pnpm build:docs` succeeds.

## Code conventions

- **Rust**: edition 2024 on the pinned stable toolchain, clippy-clean with no `unwrap`/`expect` outside tests; unit tests beside the module as `<module>_unit.rs`, run one with `cargo test <name>` from `rust/`.
- **TypeScript**: strict mode is on. Don't use `any`, `as`-casts, or non-null `!` without permission. Full ESM imports with the `.ts` extension; tests beside the module as `<module>.unit.ts`.
- **Comments**: docs on public items only. A comment must name the mistake it prevents, stays under about three lines, and comments never outnumber code; rationale and history belong in git.
- **Docs pages**: semantic HTML and the documented `htmlpress-*` components, no inline styles.
- **Formatting**: `pnpm format`. 2-space indent, no semicolons, double quotes (LF, UTF-8); rustfmt at 90 columns.

## Proposing a change

1. **Open an issue first** for anything bigger than a typo or a small bug. It's easier to align on direction before code.

2. **Branch from `main`** with a short descriptive name (e.g. `fix-fragment-title`, `feat-search`).

3. **Write a focused commit history**. Htmlpress uses [Conventional Commits](https://www.conventionalcommits.org/); the prefix drives release-please:

   - `fix:` bug fix in what an installer gets → patch release
   - `feat:` new feature in what an installer gets → minor release
   - `chore:`, `docs:`, `refactor:`, `test:`, `perf:` → no release; use these for CI, tooling, dependencies, the docs site, the shipped skills and anything else an installer never sees
   - never mark a commit breaking (`!` or a `BREAKING CHANGE:` footer)

4. **Run `pnpm test`** before pushing.

5. **Open a PR** against `main`. Describe the *why* (link the issue), include a screenshot or CLI output for any visible change, and call out anything reviewers should pay extra attention to.

## Releases

Releases are driven by [release-please](https://github.com/googleapis/release-please): a `feat` or `fix` landing on `main` opens a Release PR, and merging it cuts the version, tags it, builds the binary for every platform and publishes it to crates.io, npm and PyPI at one shared version. Maintainers never bump versions by hand; `.release/manifest.json` is maintained by release-please.

## Where to find help

- **Bugs and feature requests**: [GitHub Issues](https://github.com/datisthq/htmlpress/issues)
- **Discussion / ideas**: [GitHub Discussions](https://github.com/datisthq/htmlpress/discussions)
- **Source**: [github.com/datisthq/htmlpress](https://github.com/datisthq/htmlpress)

Thanks for helping make htmlpress better.
