|
| 1 | +# Contributing to @keito-ai/sdk |
| 2 | + |
| 3 | +Thanks for your interest in contributing! This guide covers everything you need to get started. |
| 4 | + |
| 5 | +## Table of Contents |
| 6 | + |
| 7 | +- [Before You Start](#before-you-start) |
| 8 | +- [Development Setup](#development-setup) |
| 9 | +- [Project Structure](#project-structure) |
| 10 | +- [Code Style](#code-style) |
| 11 | +- [Conventional Commits](#conventional-commits) |
| 12 | +- [Testing](#testing) |
| 13 | +- [Pull Request Process](#pull-request-process) |
| 14 | +- [Adding a New Resource](#adding-a-new-resource) |
| 15 | +- [Regenerating Types](#regenerating-types) |
| 16 | +- [Release Process](#release-process) |
| 17 | + |
| 18 | +## Before You Start |
| 19 | + |
| 20 | +- Check [open issues](https://github.com/osodevops/keito-node/issues) before starting work — someone may already be on it. |
| 21 | +- For large changes, open an issue first to discuss the approach. |
| 22 | +- All contributions are released under the [MIT License](LICENSE). |
| 23 | + |
| 24 | +## Development Setup |
| 25 | + |
| 26 | +**Prerequisites:** Node.js 18+, npm |
| 27 | + |
| 28 | +```bash |
| 29 | +# Clone the repo |
| 30 | +git clone https://github.com/osodevops/keito-node.git |
| 31 | +cd keito-node |
| 32 | + |
| 33 | +# Install dependencies |
| 34 | +npm install |
| 35 | + |
| 36 | +# Generate types from OpenAPI spec |
| 37 | +npm run generate |
| 38 | + |
| 39 | +# Run the full validation pipeline |
| 40 | +npm run typecheck |
| 41 | +npm test |
| 42 | +npm run build |
| 43 | +``` |
| 44 | + |
| 45 | +### Development Scripts |
| 46 | + |
| 47 | +| Script | Description | |
| 48 | +|--------|-------------| |
| 49 | +| `npm run generate` | Regenerate types from the OpenAPI spec | |
| 50 | +| `npm run typecheck` | Run TypeScript type checking | |
| 51 | +| `npm test` | Run all tests with Vitest | |
| 52 | +| `npm run test:watch` | Run tests in watch mode | |
| 53 | +| `npm run build` | Build ESM + CJS output to `dist/` | |
| 54 | + |
| 55 | +## Project Structure |
| 56 | + |
| 57 | +``` |
| 58 | +src/ |
| 59 | + index.ts # Public exports — everything users import |
| 60 | + client.ts # Keito class — wires resources to HTTP client |
| 61 | + version.ts # SDK version constant |
| 62 | + core/ |
| 63 | + http.ts # Fetch wrapper, retries, auth, timeout |
| 64 | + errors.ts # Typed error hierarchy |
| 65 | + pagination.ts # Auto-pagination iterator |
| 66 | + generated/ |
| 67 | + openapi.d.ts # AUTO-GENERATED — do not edit by hand |
| 68 | + models.ts # Re-export aliases for generated schema types |
| 69 | + resources/ |
| 70 | + time-entries.ts # One file per API resource |
| 71 | + expenses.ts |
| 72 | + ... |
| 73 | + outcome-types.ts # OutcomeTypes constants (F8) |
| 74 | +tests/ |
| 75 | + client.test.ts # Client instantiation tests |
| 76 | + index.test.ts # Public API export validation |
| 77 | + core/ |
| 78 | + http.test.ts # HTTP client tests (retries, errors, timeouts) |
| 79 | + pagination.test.ts # Pagination iterator tests |
| 80 | + resources/ |
| 81 | + *.test.ts # One test file per resource |
| 82 | +``` |
| 83 | + |
| 84 | +### What's auto-generated vs hand-written |
| 85 | + |
| 86 | +| Layer | Source | Changes when... | |
| 87 | +|-------|--------|----------------| |
| 88 | +| `src/generated/openapi.d.ts` | Auto-generated by `openapi-typescript` | Any spec field/schema changes | |
| 89 | +| `src/generated/models.ts` | Hand-written re-exports | New schemas added to spec | |
| 90 | +| `src/core/*` | Hand-written | Rarely (stable infrastructure) | |
| 91 | +| `src/resources/*` | Hand-written, referencing generated types | New endpoints added to spec | |
| 92 | + |
| 93 | +**Never edit `src/generated/openapi.d.ts` by hand.** Run `npm run generate` instead. |
| 94 | + |
| 95 | +## Code Style |
| 96 | + |
| 97 | +- **TypeScript strict mode** — no `any`, no implicit returns, no unused variables. |
| 98 | +- **Snake_case for API fields** — matches the API. No camelCase transformation. |
| 99 | +- **`const` over `let`** — use `let` only when reassignment is necessary. |
| 100 | +- **Explicit return types** on public methods. |
| 101 | +- **No runtime dependencies** — the SDK uses only native `fetch`. Keep it that way. |
| 102 | +- **Arrays as `T[]`** not `Array<T>`. |
| 103 | +- **Interfaces for public params** — use `interface` for method parameter types (e.g. `ListTimeEntriesParams`), `type` for aliases. |
| 104 | + |
| 105 | +## Conventional Commits |
| 106 | + |
| 107 | +All commit messages **must** follow the [Conventional Commits](https://www.conventionalcommits.org/) format. This drives automatic versioning and changelog generation via release-please. |
| 108 | + |
| 109 | +``` |
| 110 | +<type>[optional scope]: <description> |
| 111 | +
|
| 112 | +[optional body] |
| 113 | +
|
| 114 | +[optional footer(s)] |
| 115 | +``` |
| 116 | + |
| 117 | +### Commit Types |
| 118 | + |
| 119 | +| Type | Description | Version bump | |
| 120 | +|------|-------------|-------------| |
| 121 | +| `feat` | New feature | Minor (0.x: patch) | |
| 122 | +| `fix` | Bug fix | Patch | |
| 123 | +| `docs` | Documentation only | None | |
| 124 | +| `test` | Adding or updating tests | None | |
| 125 | +| `chore` | Maintenance (CI, deps, config) | None | |
| 126 | +| `refactor` | Code change that neither fixes a bug nor adds a feature | None | |
| 127 | +| `perf` | Performance improvement | Patch | |
| 128 | + |
| 129 | +### Examples |
| 130 | + |
| 131 | +``` |
| 132 | +feat: add client.contacts.update() method |
| 133 | +fix: handle empty metadata in pagination response |
| 134 | +test: add validation tests for reports resource |
| 135 | +docs: update README with outcome billing examples |
| 136 | +chore: bump openapi-typescript to v8 |
| 137 | +feat!: rename OutcomeLogParams.source to OutcomeLogParams.entry_source |
| 138 | +
|
| 139 | +BREAKING CHANGE: OutcomeLogParams.source has been renamed to entry_source |
| 140 | +``` |
| 141 | + |
| 142 | +A `BREAKING CHANGE` footer or `!` after the type triggers a major version bump (or minor while on 0.x). |
| 143 | + |
| 144 | +## Testing |
| 145 | + |
| 146 | +Every resource and core module must have tests. We use [Vitest](https://vitest.dev/). |
| 147 | + |
| 148 | +### Test conventions |
| 149 | + |
| 150 | +- **Mock `globalThis.fetch`** — tests never make real HTTP calls. |
| 151 | +- **One test file per source file** — `src/resources/expenses.ts` -> `tests/resources/expenses.test.ts`. |
| 152 | +- **Test what the SDK sends** — verify the correct HTTP method, URL, query params, and request body. |
| 153 | +- **Test return values** — verify the response is correctly parsed and returned. |
| 154 | +- **Use `afterEach(() => vi.restoreAllMocks())`** — always clean up mocks. |
| 155 | + |
| 156 | +### Running tests |
| 157 | + |
| 158 | +```bash |
| 159 | +# Run all tests |
| 160 | +npm test |
| 161 | + |
| 162 | +# Run in watch mode during development |
| 163 | +npm run test:watch |
| 164 | + |
| 165 | +# Run a specific test file |
| 166 | +npx vitest run tests/resources/expenses.test.ts |
| 167 | +``` |
| 168 | + |
| 169 | +### Validation checklist |
| 170 | + |
| 171 | +Before submitting a PR, ensure all of these pass: |
| 172 | + |
| 173 | +```bash |
| 174 | +npm run generate # types regenerate without errors |
| 175 | +npm run typecheck # no TypeScript errors |
| 176 | +npm test # all tests pass |
| 177 | +npm run build # produces dist/ without errors |
| 178 | +``` |
| 179 | + |
| 180 | +## Pull Request Process |
| 181 | + |
| 182 | +1. **Branch from `main`**: name your branch `feat/description`, `fix/description`, or `test/description`. |
| 183 | +2. **Write tests**: every new feature or bug fix needs tests. |
| 184 | +3. **Run the full pipeline**: `npm run generate && npm run typecheck && npm test && npm run build`. |
| 185 | +4. **Commit with conventional commits**: your PR title should also follow the format. |
| 186 | +5. **Keep PRs focused**: one logical change per PR. Split large changes into smaller PRs. |
| 187 | +6. **Link related issues**: use `closes #123` in the PR body. |
| 188 | + |
| 189 | +### PR checklist |
| 190 | + |
| 191 | +- [ ] Tests pass (`npm test`) |
| 192 | +- [ ] Type checking passes (`npm run typecheck`) |
| 193 | +- [ ] Build succeeds (`npm run build`) |
| 194 | +- [ ] Commit messages follow conventional commits |
| 195 | +- [ ] New public API surface is exported from `src/index.ts` |
| 196 | +- [ ] New resource has a corresponding test file |
| 197 | + |
| 198 | +## Adding a New Resource |
| 199 | + |
| 200 | +When a new endpoint is added to the OpenAPI spec: |
| 201 | + |
| 202 | +1. **Regenerate types**: `npm run generate` |
| 203 | +2. **Add re-exports** to `src/generated/models.ts` for any new schemas. |
| 204 | +3. **Create the resource file** at `src/resources/<resource-name>.ts`: |
| 205 | + |
| 206 | +```typescript |
| 207 | +import type { HttpClient } from '../core/http.js'; |
| 208 | +import { paginate, PaginatedResponse } from '../core/pagination.js'; |
| 209 | +import type { NewModel } from '../generated/models.js'; |
| 210 | + |
| 211 | +export interface ListNewModelsParams { |
| 212 | + page?: number; |
| 213 | + per_page?: number; |
| 214 | + // ... filters from the spec |
| 215 | +} |
| 216 | + |
| 217 | +export class NewModels { |
| 218 | + constructor(private readonly http: HttpClient) {} |
| 219 | + |
| 220 | + async list(params: ListNewModelsParams = {}): Promise<PaginatedResponse<NewModel>> { |
| 221 | + return paginate<NewModel>(this.http, '/api/v2/new_models', 'new_models', params); |
| 222 | + } |
| 223 | +} |
| 224 | +``` |
| 225 | + |
| 226 | +4. **Wire it up** in `src/client.ts` — add as a `readonly` property. |
| 227 | +5. **Export types** from `src/index.ts`. |
| 228 | +6. **Write tests** in `tests/resources/<resource-name>.test.ts`. |
| 229 | +7. **Run the full pipeline** to verify. |
| 230 | + |
| 231 | +## Regenerating Types |
| 232 | + |
| 233 | +The SDK types are auto-generated from the [Keito OpenAPI v2 spec](openapi/openapi-v2.yaml). |
| 234 | + |
| 235 | +### Locally |
| 236 | + |
| 237 | +```bash |
| 238 | +# Update the spec file in openapi/openapi-v2.yaml, then: |
| 239 | +npm run generate |
| 240 | +npm run typecheck |
| 241 | +``` |
| 242 | + |
| 243 | +### Via CI |
| 244 | + |
| 245 | +The `generate.yml` workflow can be triggered manually or via `repository_dispatch` from the keito repo. It fetches the latest spec, regenerates types, and opens a PR if anything changed. |
| 246 | + |
| 247 | +## Release Process |
| 248 | + |
| 249 | +Releases are fully automated via [release-please](https://github.com/googleapis/release-please). |
| 250 | + |
| 251 | +### How it works |
| 252 | + |
| 253 | +1. Merge PRs with conventional commit messages to `main`. |
| 254 | +2. Release-please opens a "Release PR" that bumps `package.json`, updates `CHANGELOG.md`, and updates `.release-please-manifest.json`. |
| 255 | +3. When the Release PR is merged, release-please creates a git tag and GitHub Release. |
| 256 | +4. The publish job in `release-please.yml` builds and publishes to npm. |
| 257 | + |
| 258 | +**You don't need to manually bump versions, create tags, or publish.** Just write good commit messages and merge. |
| 259 | + |
| 260 | +### Manual release (emergency) |
| 261 | + |
| 262 | +If you need to release outside the normal flow: |
| 263 | + |
| 264 | +```bash |
| 265 | +# Update version in package.json and src/version.ts |
| 266 | +npm version patch # or minor, major |
| 267 | +git push --follow-tags |
| 268 | +``` |
| 269 | + |
| 270 | +The `release-please.yml` workflow will detect the tag and publish. |
0 commit comments