Skip to content

Commit 213caff

Browse files
committed
feat: implement Phase 4 - Node.js bindings, CI/CD, and benchmarks
- Add Node.js bindings via napi-rs with TypeScript definitions - Add input size validation to prevent DoS attacks (CWE-770) - Add CI workflow with cross-platform testing (Linux, macOS, Windows) - Add release workflows for crates.io and npm - Add benchmark suite comparing Rust vs Python feedparser - Add .npmignore and optimize package distribution - Update README with Node.js usage examples Performance results: - Small feeds (2.7 KB): ~83x faster than Python feedparser - Medium feeds (23.9 KB): ~66x faster - Large feeds (237.3 KB): ~63x faster
1 parent 158fbfe commit 213caff

File tree

33 files changed

+12346
-79
lines changed

33 files changed

+12346
-79
lines changed

.github/workflows/ci.yml

Lines changed: 201 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,201 @@
1+
name: CI
2+
3+
on:
4+
push:
5+
branches: [main]
6+
pull_request:
7+
branches: [main]
8+
9+
env:
10+
CARGO_TERM_COLOR: always
11+
CARGO_INCREMENTAL: 0
12+
CARGO_NET_RETRY: 10
13+
RUST_BACKTRACE: short
14+
RUSTUP_MAX_RETRIES: 10
15+
16+
# Cancel previous runs on new push
17+
concurrency:
18+
group: ${{ github.workflow }}-${{ github.ref }}
19+
cancel-in-progress: true
20+
21+
jobs:
22+
# Fast checks - run once, fail fast
23+
lint:
24+
name: Lint (fmt + clippy)
25+
runs-on: ubuntu-latest
26+
timeout-minutes: 10
27+
steps:
28+
- uses: actions/checkout@v4
29+
30+
- name: Install Rust stable
31+
uses: dtolnay/rust-toolchain@stable
32+
with:
33+
components: clippy
34+
35+
- name: Install Rust nightly
36+
uses: dtolnay/rust-toolchain@nightly
37+
with:
38+
components: rustfmt
39+
40+
- name: Cache Cargo
41+
uses: Swatinem/rust-cache@v2
42+
with:
43+
shared-key: "lint"
44+
save-if: ${{ github.ref == 'refs/heads/main' }}
45+
46+
- name: Check formatting
47+
run: cargo +nightly fmt --all -- --check
48+
49+
- name: Clippy
50+
run: cargo clippy --all-targets --all-features -- -D warnings
51+
52+
- name: Check documentation
53+
run: cargo doc --no-deps --all-features
54+
env:
55+
RUSTDOCFLAGS: "-D warnings"
56+
57+
# Cross-platform Rust tests
58+
test-rust:
59+
name: Test Rust (${{ matrix.os }})
60+
runs-on: ${{ matrix.os }}
61+
timeout-minutes: 30
62+
strategy:
63+
fail-fast: false
64+
matrix:
65+
os: [ubuntu-latest, macos-latest, windows-latest]
66+
67+
steps:
68+
- uses: actions/checkout@v4
69+
70+
- name: Install Rust stable
71+
uses: dtolnay/rust-toolchain@stable
72+
73+
- name: Install nextest
74+
uses: taiki-e/install-action@nextest
75+
76+
- name: Cache Cargo
77+
uses: Swatinem/rust-cache@v2
78+
with:
79+
shared-key: "test-rust-${{ matrix.os }}"
80+
save-if: ${{ github.ref == 'refs/heads/main' }}
81+
82+
- name: Build
83+
run: cargo build --all-features
84+
85+
- name: Run tests
86+
run: cargo nextest run --all-features --no-fail-fast
87+
88+
- name: Run doctests
89+
run: cargo test --doc --all-features
90+
91+
# Node.js bindings tests
92+
test-node:
93+
name: Test Node.js (${{ matrix.os }} - Node ${{ matrix.node }})
94+
runs-on: ${{ matrix.os }}
95+
timeout-minutes: 20
96+
strategy:
97+
fail-fast: false
98+
matrix:
99+
os: [ubuntu-latest, macos-latest, windows-latest]
100+
node: [18, 20, 22]
101+
102+
steps:
103+
- uses: actions/checkout@v4
104+
105+
- name: Install Rust stable
106+
uses: dtolnay/rust-toolchain@stable
107+
108+
- name: Cache Cargo
109+
uses: Swatinem/rust-cache@v2
110+
with:
111+
shared-key: "test-node-${{ matrix.os }}"
112+
save-if: ${{ github.ref == 'refs/heads/main' }}
113+
workspaces: crates/feedparser-rs-node
114+
115+
- name: Setup Node.js ${{ matrix.node }}
116+
uses: actions/setup-node@v4
117+
with:
118+
node-version: ${{ matrix.node }}
119+
cache: 'npm'
120+
cache-dependency-path: crates/feedparser-rs-node/package-lock.json
121+
122+
- name: Install dependencies
123+
working-directory: crates/feedparser-rs-node
124+
run: npm ci
125+
126+
- name: Build native module
127+
working-directory: crates/feedparser-rs-node
128+
run: npm run build
129+
130+
- name: Test
131+
working-directory: crates/feedparser-rs-node
132+
run: npm test
133+
134+
# Code coverage (Linux only)
135+
coverage:
136+
name: Code Coverage
137+
runs-on: ubuntu-latest
138+
timeout-minutes: 20
139+
steps:
140+
- uses: actions/checkout@v4
141+
142+
- name: Install Rust stable
143+
uses: dtolnay/rust-toolchain@stable
144+
145+
- name: Cache Cargo
146+
uses: Swatinem/rust-cache@v2
147+
with:
148+
shared-key: "coverage"
149+
150+
- name: Install cargo-tarpaulin
151+
uses: taiki-e/install-action@cargo-tarpaulin
152+
153+
- name: Generate coverage
154+
run: cargo tarpaulin --out xml --all-features --engine llvm
155+
156+
- name: Upload coverage to Codecov
157+
uses: codecov/codecov-action@v4
158+
with:
159+
files: ./cobertura.xml
160+
fail_ci_if_error: false
161+
token: ${{ secrets.CODECOV_TOKEN }}
162+
163+
# MSRV check
164+
msrv:
165+
name: Check MSRV (1.86.0)
166+
runs-on: ubuntu-latest
167+
timeout-minutes: 15
168+
steps:
169+
- uses: actions/checkout@v4
170+
171+
- name: Install Rust 1.86.0
172+
uses: dtolnay/rust-toolchain@master
173+
with:
174+
toolchain: "1.86.0"
175+
176+
- name: Cache Cargo
177+
uses: Swatinem/rust-cache@v2
178+
with:
179+
shared-key: "msrv"
180+
181+
- name: Check with MSRV
182+
run: cargo +1.86.0 check --all-features
183+
184+
# All checks passed gate
185+
ci-success:
186+
name: CI Success
187+
needs: [lint, test-rust, test-node, coverage, msrv]
188+
runs-on: ubuntu-latest
189+
if: always()
190+
steps:
191+
- name: Check all jobs
192+
run: |
193+
if [[ "${{ needs.lint.result }}" != "success" ]] || \
194+
[[ "${{ needs.test-rust.result }}" != "success" ]] || \
195+
[[ "${{ needs.test-node.result }}" != "success" ]] || \
196+
[[ "${{ needs.coverage.result }}" != "success" ]] || \
197+
[[ "${{ needs.msrv.result }}" != "success" ]]; then
198+
echo "One or more jobs failed"
199+
exit 1
200+
fi
201+
echo "All CI jobs passed successfully!"
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
name: Release crates.io
2+
3+
on:
4+
push:
5+
tags:
6+
- 'v*'
7+
8+
permissions:
9+
contents: write
10+
11+
jobs:
12+
publish:
13+
name: Publish to crates.io
14+
runs-on: ubuntu-latest
15+
16+
steps:
17+
- uses: actions/checkout@v4
18+
19+
- name: Install Rust
20+
uses: dtolnay/rust-toolchain@stable
21+
22+
- name: Login to crates.io
23+
run: cargo login ${{ secrets.CARGO_REGISTRY_TOKEN }}
24+
25+
- name: Publish feedparser-rs-core
26+
run: cargo publish -p feedparser-rs-core --allow-dirty
27+
28+
- name: Wait for crates.io propagation
29+
run: sleep 30
30+
31+
- name: Create GitHub Release
32+
uses: softprops/action-gh-release@v1
33+
with:
34+
generate_release_notes: true
35+
body_path: CHANGELOG.md

.github/workflows/release-npm.yml

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
name: Release npm
2+
3+
on:
4+
push:
5+
tags:
6+
- 'v*'
7+
8+
permissions:
9+
contents: read
10+
11+
jobs:
12+
build:
13+
name: Build ${{ matrix.target }}
14+
runs-on: ${{ matrix.os }}
15+
strategy:
16+
fail-fast: false
17+
matrix:
18+
include:
19+
- os: ubuntu-latest
20+
target: x86_64-unknown-linux-gnu
21+
22+
- os: ubuntu-latest
23+
target: aarch64-unknown-linux-gnu
24+
25+
- os: macos-latest
26+
target: x86_64-apple-darwin
27+
28+
- os: macos-latest
29+
target: aarch64-apple-darwin
30+
31+
- os: windows-latest
32+
target: x86_64-pc-windows-msvc
33+
34+
steps:
35+
- uses: actions/checkout@v4
36+
37+
- name: Install Rust
38+
uses: dtolnay/rust-toolchain@stable
39+
with:
40+
targets: ${{ matrix.target }}
41+
42+
- name: Setup Node.js
43+
uses: actions/setup-node@v4
44+
with:
45+
node-version: 20
46+
registry-url: 'https://registry.npmjs.org'
47+
48+
- name: Install dependencies
49+
working-directory: crates/feedparser-rs-node
50+
run: npm install
51+
52+
- name: Build
53+
working-directory: crates/feedparser-rs-node
54+
run: npm run build -- --target ${{ matrix.target }}
55+
56+
- name: Upload artifacts
57+
uses: actions/upload-artifact@v4
58+
with:
59+
name: bindings-${{ matrix.target }}
60+
path: crates/feedparser-rs-node/*.node
61+
62+
publish:
63+
name: Publish to npm
64+
runs-on: ubuntu-latest
65+
needs: build
66+
67+
steps:
68+
- uses: actions/checkout@v4
69+
70+
- name: Setup Node.js
71+
uses: actions/setup-node@v4
72+
with:
73+
node-version: 20
74+
registry-url: 'https://registry.npmjs.org'
75+
76+
- name: Download artifacts
77+
uses: actions/download-artifact@v4
78+
with:
79+
path: crates/feedparser-rs-node/artifacts
80+
81+
- name: Install dependencies
82+
working-directory: crates/feedparser-rs-node
83+
run: npm install
84+
85+
- name: Publish to npm
86+
working-directory: crates/feedparser-rs-node
87+
run: npm publish --access public
88+
env:
89+
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}

CHANGELOG.md

Lines changed: 40 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,46 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
88
## [Unreleased]
99

1010
### Added
11+
- Node.js bindings via napi-rs
12+
- npm package `feedparser-rs`
13+
- Criterion benchmarks for Rust
14+
- CI/CD pipeline with GitHub Actions
15+
- Cross-platform builds (Linux, macOS, Windows)
16+
- TypeScript definitions
17+
- Comprehensive Node.js test suite
18+
- Benchmark comparison infrastructure
19+
- Python feedparser benchmark baseline
1120

12-
- Initial project structure (Phase 1)
13-
- Core type definitions: `ParsedFeed`, `FeedMeta`, `Entry`, `FeedVersion`
14-
- Error handling with `thiserror`
15-
- Workspace setup with Edition 2024
21+
### Changed
22+
- N/A
1623

17-
## [0.1.0] - TBD
24+
### Deprecated
25+
- N/A
1826

19-
Initial release.
27+
### Removed
28+
- N/A
29+
30+
### Fixed
31+
- N/A
32+
33+
### Security
34+
- N/A
35+
36+
## [0.1.0] - 2025-12-14
37+
38+
### Added
39+
- Initial release
40+
- RSS 2.0, 1.0, 0.9x parsing
41+
- Atom 1.0, 0.3 parsing
42+
- JSON Feed 1.0, 1.1 parsing
43+
- Multi-format date parsing
44+
- HTML sanitization
45+
- Encoding detection
46+
- Tolerant parsing with bozo flag
47+
- Rust core library `feedparser-rs-core`
48+
- Parser limits for security (max nesting depth, entry count, etc.)
49+
- Comprehensive test coverage
50+
- Documentation with examples
51+
52+
[Unreleased]: https://github.com/bug-ops/feedparser-rs/compare/v0.1.0...HEAD
53+
[0.1.0]: https://github.com/bug-ops/feedparser-rs/releases/tag/v0.1.0

0 commit comments

Comments
 (0)