Contributing
Contributions are very welcome! Whether it is a bug fix, a new feature, improved documentation, or just a question — please feel free to open an issue or pull request.
Getting the Code
git clone https://github.com/psachmann/fetchling.git
cd fetchling
Setting Up Your Dev Environment
You need:
| Tool | Version |
|---|---|
| .NET SDK | 10.0 (see global.json) |
| CSharpier | 1.3.0 (installed as a local dotnet tool) |
After cloning, restore the local tools and packages:
dotnet tool restore
dotnet restore
Git Hooks
The repo ships husky hook definitions in .husky/, but no
project wires them up automatically — install them once per clone:
dotnet husky install
This adds a pre-commit hook that runs dotnet csharpier format on staged files, and a pre-push hook
that runs the full test suite (dotnet test -c Release). Both mirror checks CI also runs, so the
hooks just let you catch a formatting or test failure before pushing instead of after.
Common Commands
# Build
dotnet build
# Run all tests
dotnet test
# Run tests with coverage
dotnet test --configuration Release -- --coverage --coverage-output-format cobertura
# Check formatting
dotnet csharpier check .
# Fix formatting
dotnet csharpier format .
# Build the documentation site
dotnet docfx docs/docfx.json
Code Style
The project uses CSharpier for formatting, enforced as a CI gate and as a husky pre-commit hook (see Git Hooks below). Run dotnet csharpier format . locally before pushing to avoid the CI failing on formatting.
There is no extensive style guide beyond what CSharpier enforces — just try to follow the patterns already present in the codebase.
Build Gates
Four gates guard the shipping surface under src/ (tests and samples are unaffected). Any of them can
turn a change that builds locally into a Release-build failure, so it's worth knowing what each one is
before your PR trips it:
Public API tracking — every
src/project (except DevTools) lists its entire public surface inPublicAPI.Shipped.txt/PublicAPI.Unshipped.txt. Adding, removing, or changing a public member fails the Release build (RS0016/RS0017) until you add or remove the line. Apply the IDE's "Add to public API" code fix, or run:dotnet format analyzers Fetchling.slnx --diagnostics RS0016 --severity warnThis can't touch
.razorfiles —<Suspense>-style component members go intosrc/Fetchling.Blazor/PublicAPI.Unshipped.txtby hand, using the exact line theRS0016message gives you.Package validation —
dotnet packdiffs each assembly against the last stable release and fails on an unintentional breaking change. It only runs onpack, notbuild, so rundotnet pack -c Releaselocally if your change touches a public member. An intentional break needs a suppression file, not a weakened gate:dotnet pack -c Release -p:ApiCompatGenerateSuppressionFile=trueBanned symbols (
src/BannedSymbols.txt) — bans ambient time (DateTime.Now/.UtcNow, etc.) and blocking waits (Thread.Sleep,Task.Delay) asRS0030. All time insrc/must flow through the injectedISchedulerso tests can drive it withTestScheduler— this gate is what catches a strayDateTime.UtcNowbefore it ships. Test code is exempt.Trim / AOT analyzers — every
src/project except DevTools claims trim and AOT compatibility. A construct that can't be statically analyzed for trimming fails the Release build rather than surfacing later as a runtime failure in a consumer's trimmed or AOT-published app.
Making Changes
- Fork the repository and create a branch from
main. - Write tests for your change. All new behavior should be covered by tests.
- Run the full test suite to make sure nothing is broken:
dotnet test. - Check formatting:
dotnet csharpier check . - Open a pull request against
mainwith a clear description of what you changed and why.
Adding a New Feature
If you are thinking about a larger feature or a change to the public API, please open an issue first to discuss it. That saves everyone time and avoids the frustration of a PR being declined after significant work.
Fixing a Bug
- Open an issue describing the bug (if one does not exist already).
- Add a failing test that reproduces the bug.
- Fix the bug so the test passes.
- Open a pull request linking to the issue.
Making a Breaking Change
Breaking changes only ship in a major release, so please open an issue first. Once one is agreed, document it in the migration guide in the same pull request — you know the why and the fix better than anyone will at release time.
The guide is docs/doc/migrating-to-v<N>.md, where <N> is the upcoming major (currently migrating-to-v3.md). Your PR owes it an entry if it:
- adds a suppression to a
CompatibilitySuppressions.xmlor a*REMOVED*line to aPublicAPI.Unshipped.txt(see Build Gates), or - changes behaviour or telemetry (names, units, tags) in a way existing code still compiles against. No gate catches these, which is exactly why they need writing down.
Each entry is a row in the "At a Glance" table plus a section under "Breaking Changes" with what changed, the error or symptom a consumer sees, a Why:, and a How to migrate: — with before/after code where it applies. migrating-to-v2.md shows the tone and depth to aim for.
If yours is the first break of a new major, create the file and add it to docs/doc/toc.yml above the previous guide.
There is no changelog file to update: release notes are generated by GitHub from pull request titles, so a clear PR title is all they need.
Which section of the notes a PR lands in is decided by its label, configured in
.github/release.yml: breaking-change,
enhancement and bug get their own sections, anything else falls under "Other Changes", and Dependabot
PRs (label dependencies) are left out. Labels are read when the release is published, so they can still be
added to merged PRs afterwards.
Improving Documentation
Documentation lives in the docs/doc/ directory as Markdown files. Changes to documentation are just as valuable as code changes — please feel free to open PRs for typos, unclear explanations, missing examples, or anything else.
Running the Documentation Site Locally
DocFX is already restored as a local tool by dotnet tool restore (see Setting Up Your Dev
Environment).
# Build and serve locally
dotnet docfx docs/docfx.json --serve
Then open http://localhost:8080 in your browser.
CI / CD
The build pipeline runs on every push and pull request to main:
- Restore tools and packages
- CSharpier format check
- Release build (warnings are treated as errors in Release mode)
- Tests with code coverage (Cobertura format)
- Coverage upload to Codecov
Pull requests must pass all CI checks before merging.
Release Process (Maintainers)
Cutting a stable release
- Public API files: move the accumulated
PublicAPI.Unshipped.txtadditions intoPublicAPI.Shipped.txt, delete each*REMOVED*line together with itsShippedcounterpart, and leaveUnshippedwith only its#nullable enableheader. Prereleases leave both files alone. Members whose only change since 2.0.0 is nullability annotation (!,?,~) already appear inShippedin their current form: the analyzer matches symbols ignoring annotations and would reject a*REMOVED*/re-add pair (RS0025,RS0050,RS0036). - Package validation: set
PackageValidationBaselineVersionto the new stable version (currently unset, because no stableFetchling.*package exists; the 2.xDotNetQuery.*assemblies share no names). The baseline is deliberately the last stable release, not the latest prerelease. For an intentional break, generate a suppression file instead of weakening the gate:mise run suppressions. - Docs: remove the "not released yet" note from
migrating-to-v3.md, drop the "the upcoming N.0" wording from other docs, and link the guide fromREADME.md,docs/index.mdand the upgrade paragraph ofdocs/llms.txt. - Context7: add the new tag to
previousVersionsincontext7.json(keep the previous one) and update the versioned ID inREADME.mdanddocs/llms.txt. A push tomaintouching indexed paths re-indexes via.github/workflows/context7.yaml(needsCONTEXT7_API_KEY).
Migration guide
docs/doc/migrating-to-v<N>.md is written in the PR that makes the break. Each entry is a row in the "At a Glance" table (change / affects you if… / what to do) plus a "Breaking Changes" section with what changed, the symptom a consumer sees, a Why: and a How to migrate: with before/after code. When the first break of a new major lands, create the file and add it to docs/doc/toc.yml above the previous guide. The docs site deploys from main, so open the guide with a "not released yet" note.
Release notes come from PR titles (generate_release_notes: true); grouping is in .github/release.yml (labels breaking-change / enhancement / bug; dependencies and Dependabot PRs are excluded).
Other gate details
TrackPublicApiandIsAotCompatiblehave no default;RequireExplicitProjectPropertiesfails the build if asrc/project omits either, so an opt-out is always visible.Fetchling.Blazor.DevToolssetsTrackPublicApi=false(a drop-in debugging component, not an API to program against) andIsAotCompatible=false(its inspector reflection-serializes arbitrary consumer data, trippingIL2026/IL3050; under trimmingJsonSerializeremits silently-empty JSON).RS0041is suppressed inFetchling.Blazor(Razor-generatedBuildRenderTreeoverrides use oblivious reference types).- To ban another API, add a documentation-comment-ID line (
P:/M:with full parameter list /T:), then;and the message, tosrc/BannedSymbols.txt.
Reporting Issues
Found a bug? Have a question? Please open an issue with:
- a clear description of the problem or question,
- a minimal reproduction (if it is a bug),
- the .NET version and platform you are using.
License
By contributing to Fetchling, you agree that your contributions will be licensed under the MIT License.