Contributing
Fetcher is an open-source project licensed under Apache 2.0. This guide covers everything you need to get the project running locally and submit high-quality pull requests.
Development Setup
Prerequisites
| Requirement | Minimum Version | Check |
|---|---|---|
| Node.js | 18.0.0 | node --version |
| pnpm | 10.33.0 | pnpm --version |
| Git | 2.x | git --version |
Fetcher uses ES modules exclusively ("type": "module") and targets Node.js >= 18 for native Fetch API support.
Getting Started
# Fork and clone
git clone https://github.com/<your-username>/fetcher.git
cd fetcher
# Install all dependencies across the monorepo
pnpm install
# Build all packages
pnpm build
# Run unit tests to verify the setup
pnpm test:unitRepository Structure
graph TD
subgraph Repo["fetcher (monorepo root)"]
style Repo fill:#161b22,stroke:#30363d,color:#e6edf3
root["package.json<br>pnpm-workspace.yaml<br>tsconfig.json<br>.prettierrc"]
subgraph Packages["packages/"]
style Packages fill:#161b22,stroke:#30363d,color:#e6edf3
fetcher["fetcher/<br>src/ dist/ package.json"]
decorator["decorator/<br>src/ dist/ package.json"]
eventbus["eventbus/<br>src/ dist/ package.json"]
eventstream["eventstream/<br>src/ dist/ package.json"]
openai["openai/<br>src/ dist/ package.json"]
openapi["openapi/<br>src/ dist/ package.json"]
generator["generator/<br>src/ dist/ package.json"]
react["react/<br>src/ dist/ package.json"]
storage["storage/<br>src/ dist/ package.json"]
cosec["cosec/<br>src/ dist/ package.json"]
wow["wow/<br>src/ dist/ package.json"]
viewer["viewer/<br>src/ dist/ package.json"]
end
subgraph Integration["integration-test/"]
style Integration fill:#161b22,stroke:#30363d,color:#e6edf3
it["Real API tests"]
end
endEach package follows the same internal layout:
packages/<name>/
src/ # TypeScript source files
dist/ # Build output (ESM + UMD + types)
package.json # Package-specific config and scripts
vite.config.ts # Vite build config
tsconfig.json # TypeScript config extending rootMonorepo Conventions
Workspace Protocol
Dependency versions are centralized via the catalog: protocol in pnpm-workspace.yaml. Packages reference catalog: instead of hardcoding version ranges:
# pnpm-workspace.yaml
catalog:
vitest: ^4.1.5
vite: ^8.0.11
typescript: ^6.0.3// packages/fetcher/package.json
{
"devDependencies": {
"vitest": "catalog:",
"vite": "catalog:"
}
}This ensures all packages use the same dependency versions.
Build Configuration
All packages use Vite for building with unplugin-dts for type declarations. Each package outputs:
| Output | File | Format |
|---|---|---|
| ESM | dist/index.es.js | ES modules |
| UMD | dist/index.umd.js | Universal module |
| Types | dist/index.d.ts | TypeScript declarations |
Packages with React components (viewer, react) additionally use @vitejs/plugin-react with React Compiler and @babel/plugin-proposal-decorators (legacy mode).
Package Scripts
Every package supports these scripts:
# Build a single package
pnpm --filter @ahoo-wang/fetcher build
# Test a single package
pnpm --filter @ahoo-wang/fetcher test
# Lint a single package
pnpm --filter @ahoo-wang/fetcher lintOr use root-level commands to operate on all packages:
pnpm build # Build all packages
pnpm test:unit # Unit tests for all packages
pnpm test:it # Integration tests only
pnpm lint # Lint all packages
pnpm format # Format all files with Prettier
pnpm clean # Clean all dist/ directoriesTesting
Fetcher uses Vitest as its test runner with multiple testing strategies:
flowchart TD
subgraph TestStrategies["Testing Strategies"]
style TestStrategies fill:#161b22,stroke:#30363d,color:#e6edf3
subgraph Unit["Unit Tests"]
style Unit fill:#161b22,stroke:#30363d,color:#e6edf3
U1["Vitest + MSW<br>packages/*/src/*.test.ts"]
end
subgraph Browser["Browser Tests"]
style Browser fill:#161b22,stroke:#30363d,color:#e6edf3
B1["@vitest/browser + Playwright<br>viewer package"]
end
subgraph Integration["Integration Tests"]
style Integration fill:#161b22,stroke:#30363d,color:#e6edf3
I1["Real API calls<br>integration-test/"]
end
subgraph Storybook["Component Docs"]
style Storybook fill:#161b22,stroke:#30363d,color:#e6edf3
S1["Storybook<br>viewer + react packages"]
end
endUnit Tests
Unit tests live alongside source files with a *.test.ts or *.test.tsx suffix. Vitest globals are enabled (describe, it, expect, vi available without imports):
// Example: packages/fetcher/src/fetcher.test.ts
import { Fetcher } from './fetcher';
describe('Fetcher', () => {
it('should create with default options', () => {
const fetcher = new Fetcher();
expect(fetcher.headers).toEqual({ 'Content-Type': 'application/json' });
});
});The fetcher package uses MSW (Mock Service Worker) for HTTP mocking:
import { http, HttpResponse } from 'msw';
import { setupServer } from 'msw/node';
const server = setupServer(
http.get('https://api.example.com/users', () => {
return HttpResponse.json([{ id: 1, name: 'Alice' }]);
}),
);
beforeAll(() => server.listen());
afterEach(() => server.resetHandlers());
afterAll(() => server.close());Running Tests
# All unit tests
pnpm test:unit
# Single package
pnpm --filter @ahoo-wang/fetcher test
# Single test file
pnpm --filter @ahoo-wang/fetcher vitest run src/fetcher.test.ts
# With coverage
pnpm --filter @ahoo-wang/fetcher vitest run --coverage
# Integration tests (requires network)
pnpm test:itBrowser Tests
The viewer package runs tests in a real browser via @vitest/browser with Playwright:
pnpm --filter @ahoo-wang/fetcher-viewer testCoverage
All packages use @vitest/coverage-v8 for code coverage reporting.
Test Naming Convention
| File Pattern | Purpose | ESLint |
|---|---|---|
*.test.ts | Unit tests | Ignored by ESLint |
*.test.tsx | Component tests | Ignored by ESLint |
ESLint is configured to skip test files, so test files are not linted.
Code Style
Fetcher enforces consistent code style through Prettier and ESLint.
Prettier Configuration
The .prettierrc at the repository root:
{
"semi": true,
"trailingComma": "all",
"singleQuote": true,
"printWidth": 80,
"tabWidth": 2,
"useTabs": false,
"bracketSpacing": true,
"arrowParens": "avoid"
}Run the formatter before committing:
pnpm formatESLint
ESLint uses @typescript-eslint with the following key rules:
| Rule | Setting | Notes |
|---|---|---|
@typescript-eslint/no-explicit-any | OFF | any is allowed |
@typescript-eslint/consistent-type-imports | prefer: "type-imports" | Enforced in integration-test and story files |
Run the linter:
pnpm lintTypeScript
All packages use strict TypeScript mode. The root tsconfig.json enables:
experimentalDecorators-- Required by@ahoo-wang/fetcher-decoratoremitDecoratorMetadata-- Required for reflect-metadata supportstrict: true-- Full strict mode
License Headers
Every source file must include the Apache 2.0 license header:
/*
* Copyright [2021-present] [ahoo wang <ahoowang@qq.com> (https://github.com/Ahoo-Wang)].
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/Commit Messages
Follow conventional commit style:
type(scope): description
feat(fetcher): add support for custom abort controllers
fix(decorator): resolve parameter name extraction for arrow functions
test(eventstream): add integration tests for SSE parsing
chore(project): update dependencies to latest versions
docs(wiki): add configuration reference pagePull Request Process
Branch Workflow
sequenceDiagram
autonumber
participant D as Developer
participant F as Fork
participant R as Upstream/main
D->>F: Create feature branch
D->>D: Make changes, write tests
D->>D: pnpm format && pnpm lint
D->>D: pnpm test:unit
D->>F: Push branch
F->>R: Open Pull Request
Note over R: CI checks run<br>build + lint + test
R-->>D: Review feedback
D->>F: Address feedback
F->>R: Push updates
R->>R: Merge when approvedBefore Submitting
Run these checks locally before opening a PR:
# 1. Format code
pnpm format
# 2. Lint all packages
pnpm lint
# 3. Build all packages
pnpm build
# 4. Run unit tests
pnpm test:unitPR Checklist
- [ ] Code compiles without errors (
pnpm build) - [ ] All existing tests pass (
pnpm test:unit) - [ ] New features have accompanying unit tests
- [ ] Code is formatted (
pnpm format) - [ ] Linter passes (
pnpm lint) - [ ] License headers present on new files
- [ ] Commit messages follow conventional commit format
- [ ] Documentation updated if public API changed
Version Management
Fetcher uses a centralized version management script to keep all packages in sync:
# Update all package versions to a new version
pnpm update-version 3.17.0This script (scripts/update-all-versions.sh) updates the version field in every package.json across the monorepo, ensuring consistency.
The current version is maintained in the root package.json:
{
"version": "3.16.4"
}Adding a New Package
To add a new package to the monorepo:
- Create the directory under
packages/ - Add a
package.jsonwith@ahoo-wang/fetcher-<name>as the name - Add a
vite.config.tsfollowing the pattern of existing packages - Add a
tsconfig.jsonextending the root config - Use
catalog:for any shared dependencies - Update inter-package dependencies as needed
# Example: create a new package
mkdir packages/my-package
cd packages/my-package
# Create package.json, vite.config.ts, tsconfig.json, src/index.tsThe pnpm workspace configuration in pnpm-workspace.yaml automatically picks up new packages:
packages:
- packages/*
- integration-testBundle Analysis
Each package includes an analyze script that generates a visual bundle size report using vite-bundle-analyzer:
pnpm --filter @ahoo-wang/fetcher analyzeWhat to Read Next
| Topic | Page |
|---|---|
| Project overview | Introduction |
| Quick start guide | Quick Start |
| Full configuration reference | Configuration |