- Python 3.10+
- Rust 1.80+ (stable)
- uv - Python package manager (install)
- Task - Task runner (install)
- prek - Pre-commit hooks (
uv tool install prek)
- Node.js 18+
- pnpm 8+ - Fast package manager (install)
- wasm-pack - For WASM builds (
cargo install wasm-pack)
# Clone repository
git clone https://github.com/xberg-io/html-to-markdown.git
cd html-to-markdown
# Setup environment (installs deps, builds Rust, installs hooks)
task setupThis will:
- Install Python dependencies with
uv sync - Build Rust extension with maturin
- Install prek hooks for commit linting and code quality
Install the git hooks with task setup (or poly hooks install directly). On
every commit, poly runs lint, format, and file-safety checks plus cargo clippy;
the commit-msg hook validates the message. Run all hooks manually with
poly hooks run pre-commit --all-files.
# Python tests
task test:python
# Rust tests
task test:rust
# All Python + Rust tests
task test
# With coverage
task cov:all# Install dependencies first
pnpm install
# All JavaScript tests
pnpm test
# Specific packages
pnpm run test:node # NAPI-RS bindings
pnpm run test:wasm # WebAssembly bindings
pnpm run test:ts # TypeScript package
# Watch mode
cd packages/typescript
pnpm test:watch
# With coverage
cd packages/typescript
pnpm test -- --coverage# Format code (Rust + Python)
task format
# Run all linters
task lint
# Build Rust components
task build# Type checking
pnpm run typecheck
# Build all packages
pnpm run build
# Build specific targets
pnpm run build:node # NAPI-RS native bindings
pnpm run build:wasm # WebAssembly (all 3 targets)
pnpm run build:ts # TypeScript wrapper
# Clean build artifacts
pnpm run clean# Quick benchmarks
task bench
# All benchmarks
task bench:allThis is a monorepo containing multiple language bindings and distributions:
html-to-markdown/
├── pnpm-workspace.yaml # pnpm workspace configuration
├── package.json # Root workspace scripts
│
├── crates/ # Rust crates
│ ├── html-to-markdown/ # Core library (astral-tl parser)
│ ├── html-to-markdown-cli/ # Rust CLI binary
│ ├── html-to-markdown-node/ # NAPI-RS bindings for Node.js (~691k ops/sec)
│ ├── html-to-markdown-wasm/ # wasm-bindgen for browsers (~229k ops/sec)
│ └── html-to-markdown-py/ # PyO3 bindings powering the Python package
│
├── packages/ # Releasable packages
│ ├── python/ # PyPI package (html_to_markdown)
│ │ ├── html_to_markdown/ # Python sources
│ │ └── tests/ # Python integration + unit tests
│ ├── typescript/ # TypeScript package with CLI (npm)
│ └── ruby/ # Ruby gem sources/specs (RubyGems)
│
└── scripts/ # Helper scripts (wheel prep, gem prep, demo)
| Package | Registry | Description |
|---|---|---|
html-to-markdown-rs |
crates.io | Core Rust library |
html-to-markdown |
PyPI | Python package |
html-to-markdown |
npm | TypeScript package with CLI |
html-to-markdown |
RubyGems | Ruby gem (Magnus bindings) |
html-to-markdown-node |
npm | Native Node.js bindings |
html-to-markdown-wasm |
npm | WebAssembly bindings |
- Edit code in
crates/html-to-markdown/src/ - Run Rust tests:
task test:rustorcargo test - Rebuild bindings:
- Python:
task build - Node.js:
cd crates/html-to-markdown-node && pnpm run build - WASM:
cd crates/html-to-markdown-wasm && pnpm run build:all
- Python:
- Run integration tests:
task test:pythonorpnpm test
- Edit code in
packages/python/html_to_markdown/ - Update type stubs in
_rust.pyiif needed - Run tests:
task test:python
- Edit Rust code in
src/lib.rs - Rebuild:
pnpm run build(generates TypeScript types automatically) - Test:
pnpm testorcargo test
- Edit Rust code in
src/lib.rs - Rebuild:
pnpm run build:all(builds for bundler, nodejs, and web) - Test:
pnpm testorcargo test
- Edit code in
src/(library entrypoints + CLI) - Build:
pnpm run build(runs Node binding build + TypeScript emit) - Lint:
pnpm run lint - Test:
pnpm testorpnpm test:watch - Test CLI locally:
node dist/cli.js input.html
- Edit Ruby sources in
lib/and specs inspec/ - Build native extension:
bundle exec rake compile - Run specs:
bundle exec rake spec
- Rust tests: Add to
crates/*/src/lib.rsorcrates/*/tests/ - Python tests: Add to
packages/python/tests/following pytest patterns - TypeScript tests: Add to
packages/typescript/tests/using vitest - Ruby specs: Add to
packages/ruby/spec/ - Integration tests: Add to appropriate test directory
To test wheels and binaries without creating a release:
# Test wheel building manually
gh workflow run "Test Wheel Building"
# Or manually build locally
pip install cibuildwheel
cibuildwheel --output-dir wheelhouse
# Test CLI binary locally
cargo build --release --package html-to-markdown-cli
./target/release/html-to-markdown --version- ci-*.yaml: Xberg-style, path-filtered workflows (rust, python, node, wasm, ruby, php, go, java, elixir, validate)
- test-wheels.yaml: Builds and tests wheels (manual or on Rust/config changes)
- All workflows must pass before merging
Commits must follow Conventional Commits:
feat: add new feature
fix: fix bug
docs: update documentation
refactor: refactor code
test: add tests
Prek enforces this automatically via commitlint hook.
- Formatting: ruff (120 char line length)
- Linting: ruff with ALL rules enabled (see pyproject.toml for ignores)
- Type checking: mypy in strict mode
- Formatting:
cargo fmt - Linting:
cargo clippywith-D warnings - Style: Follow standard Rust conventions
- Tests: Required for all public APIs
- Formatting: Prettier via tsup
- Type checking: TypeScript 5.6+ in strict mode
- Linting: ESLint (when configured)
- Tests: vitest with coverage reporting
- Style: 2-space indentation, trailing commas
All Python/Rust checks run automatically via prek on commit.
- Fork the repository
- Create a feature branch (
git checkout -b feat/amazing-feature) - Make your changes
- Run
task testandtask lint - Commit with conventional commit format
- Push and create a pull request
- Update versions in:
-
Cargo.toml(workspace.package.version) -
packages/*/package.json -
crates/html-to-markdown-node/package.json -
crates/html-to-markdown-wasm/package.json
-
[workspace.package]
version = "2.4.2"
```
```json
// package.json files
"version": "2.4.2"
```
-
Update
CHANGELOG.mdwith changes -
Run full test suite:
task test # Python + Rust pnpm test # JavaScript/TypeScript
-
Build and verify all targets:
task build:cli && ./target/release/html-to-markdown --version pnpm run build # All JS/TS packages
-
Commit changes:
git commit -m "chore: bump version to 2.4.2"
-
Create and push tag:
git tag -a v2.4.2 -m "Release v2.4.2" git push origin v2.4.2 -
Automated workflows trigger:
release.yml- GitHub release with CLI binariesrelease-homebrew.yml- Updates Homebrew formulapublish-cargo.yml- Publishes to crates.iorelease.yaml- Publishes Python to PyPI- Manual npm publish required (see below)
-
Publish npm packages (manual):
# Login to npm (once) npm login # Publish main TypeScript package (includes CLI) cd packages/typescript pnpm publish # Publish native bindings (with pre-built binaries) cd ../../crates/html-to-markdown-node pnpm run build pnpm publish # Publish WASM cd ../html-to-markdown-wasm pnpm run build:all pnpm publish
-
Required secrets (already configured):
CARGO_TOKEN- From https://crates.io/settings/tokensHOMEBREW_TOKEN- GitHub token withreposcope- PyPI uses trusted publishing (OIDC); no
PYPI_TOKENsecret is required - npm uses trusted publishing (OIDC); no
NPM_TOKENsecret is required - NuGet uses trusted publishing (OIDC); no
NUGET_API_KEYsecret is required
Verify all distributions are published:
- Rust: https://crates.io/crates/html-to-markdown-rs
- Python: https://pypi.org/project/html-to-markdown/
- npm (main): https://www.npmjs.com/package/@xberg-io/html-to-markdown
- npm (wasm): https://www.npmjs.com/package/@xberg-io/html-to-markdown-wasm
- Homebrew: https://github.com/xberg-io/homebrew-tap
- GitHub: https://github.com/xberg-io/html-to-markdown/releases
- Issues: GitHub Issues
- Discussions: GitHub Discussions
- Discord: Xberg Community
By contributing, you agree that your contributions will be licensed under the MIT License.