|
| 1 | +# Contributing |
| 2 | + |
| 3 | +What you need to know before opening a PR against |
| 4 | +`@socketregistry/packageurl-js`. Dev setup, the test/coverage/lint |
| 5 | +workflow, the parallel-session git rules, and the pre-PR checklist. |
| 6 | + |
| 7 | +## Who this is for |
| 8 | + |
| 9 | +First-time contributors and returning ones who want to refresh on |
| 10 | +the commands and conventions. Reading order: **Setup → Making a |
| 11 | +change → Running checks → Opening the PR**. |
| 12 | + |
| 13 | +## Setup |
| 14 | + |
| 15 | +Requirements: |
| 16 | + |
| 17 | +| Tool | Version | Notes | |
| 18 | +|---|---|---| |
| 19 | +| Node.js | ≥ 22 | Built-in TypeScript stripping (`--experimental-strip-types` on 22, default on 23+). | |
| 20 | +| pnpm | ≥ 11.0.0-rc.0 | Install via `corepack enable pnpm` or `npm install -g pnpm`. | |
| 21 | +| git | ≥ 2.30 | Submodule support. | |
| 22 | + |
| 23 | +Clone and install: |
| 24 | + |
| 25 | +```bash |
| 26 | +git clone https://github.com/SocketDev/socket-packageurl-js.git |
| 27 | +cd socket-packageurl-js |
| 28 | +pnpm install |
| 29 | +``` |
| 30 | + |
| 31 | +`pnpm install` also: |
| 32 | + |
| 33 | +- Sets up the `husky` pre-commit hooks. |
| 34 | +- Initializes `upstream/meander` submodule lazily on first |
| 35 | + `pnpm tour:build`. |
| 36 | + |
| 37 | +`pnpm tour doctor` reports which external tools the build may shell |
| 38 | +out to are present vs missing on your machine. All listed tools are |
| 39 | +optional; missing ones fall back to safe defaults. |
| 40 | + |
| 41 | +## Repo layout |
| 42 | + |
| 43 | +``` |
| 44 | +socket-packageurl-js/ |
| 45 | +├── src/ ← library source (see docs/architecture.md) |
| 46 | +├── test/ ← functional tests (vitest) |
| 47 | +├── scripts/ ← build/lint/test/release scripts |
| 48 | +├── docs/ ← these docs |
| 49 | +├── val/ ← Val Town comment backend (tour-specific) |
| 50 | +├── upstream/meander/ ← submodule (tour generator) |
| 51 | +├── .config/ ← tsconfig + vitest configs |
| 52 | +├── .github/workflows/ ← CI (ci.yml, pages.yml, provenance.yml, ...) |
| 53 | +├── .claude/ ← Claude Code config (agents, skills, hooks) |
| 54 | +├── tour.json ← tour manifest |
| 55 | +└── walkthrough/ ← tour build output (gitignored) |
| 56 | +``` |
| 57 | + |
| 58 | +You almost never edit `upstream/meander/` (that's the submodule |
| 59 | +we pin) or `walkthrough/` (build output). |
| 60 | + |
| 61 | +## Making a change |
| 62 | + |
| 63 | +### 1. Branch, worktree, or sibling clone |
| 64 | + |
| 65 | +This repo expects **multiple concurrent Claude Code sessions** may |
| 66 | +be working on the same checkout. Plain `git checkout -b` inside the |
| 67 | +main clone yanks the working tree out from under any other session. |
| 68 | +Use one of: |
| 69 | + |
| 70 | +- **Worktree** — `git worktree add -b my-task ../socket-packageurl-js-my-task main` |
| 71 | +- **Sibling clone** — clone the repo elsewhere entirely |
| 72 | +- **Same clone** — only if you are sure nobody else has a session |
| 73 | + going |
| 74 | + |
| 75 | +See the `PARALLEL CLAUDE SESSIONS` section of `CLAUDE.md` for the |
| 76 | +full doctrine. |
| 77 | + |
| 78 | +### 2. Edit source |
| 79 | + |
| 80 | +Per `CLAUDE.md`: |
| 81 | + |
| 82 | +- TypeScript strict mode; type imports must be in separate |
| 83 | + `import type` lines. |
| 84 | +- No `null` except `__proto__: null` or external-API requirement — |
| 85 | + use `undefined`. |
| 86 | +- `{ __proto__: null, ...payload }` for config/return/internal |
| 87 | + objects. |
| 88 | +- No dynamic imports (`await import(...)`). |
| 89 | +- No `fetch()` — use `httpJson` / `httpText` / `httpRequest` from |
| 90 | + `@socketsecurity/lib/http-request`. |
| 91 | +- For file existence: `existsSync` from `node:fs`. For deletion: |
| 92 | + `safeDelete` / `safeDeleteSync` from `@socketsecurity/lib/fs` — |
| 93 | + never reach directly for `fs.rm` / `fs.unlink` / `fs.rmdir`. |
| 94 | +- Default to NO comments in code. Only where the WHY is |
| 95 | + non-obvious to a senior engineer. |
| 96 | +- Use the `Edit` tool for text changes; never `sed` / `awk`. |
| 97 | + |
| 98 | +### 3. Write tests |
| 99 | + |
| 100 | +Two vitest configs: |
| 101 | + |
| 102 | +| Config | When | File naming | |
| 103 | +|---|---|---| |
| 104 | +| `.config/vitest.config.mts` | Normal tests. Threads, shared memory. Fast. | `*.test.mts` | |
| 105 | +| `.config/vitest.config.isolated.mts` | Tests that mock globals via `vi.doMock`, modify `process.env`, or `process.chdir`. Forks, full isolation. | `*.isolated.test.mts` | |
| 106 | + |
| 107 | +**Test style in this repo:** functional. Tests assert behavior via |
| 108 | +the public API — inputs → outputs. Tests never read source files |
| 109 | +and assert on their contents. |
| 110 | + |
| 111 | +Example: |
| 112 | + |
| 113 | +```typescript |
| 114 | +import { test, expect } from 'vitest' |
| 115 | +import { PackageURL } from '../src/package-url.js' |
| 116 | + |
| 117 | +test('parses npm scoped package', () => { |
| 118 | + const purl = new PackageURL('pkg:npm/@scope/pkg@1.0.0') |
| 119 | + expect(purl.namespace).toBe('@scope') |
| 120 | + expect(purl.name).toBe('pkg') |
| 121 | + expect(purl.version).toBe('1.0.0') |
| 122 | +}) |
| 123 | +``` |
| 124 | + |
| 125 | +If you are adding a new ecosystem or URL parser, put tests under |
| 126 | +`test/purl-types/<name>.test.mts` or |
| 127 | +`test/url-converter/<name>.test.mts`. |
| 128 | + |
| 129 | +## Running checks |
| 130 | + |
| 131 | +The **canonical pre-PR check** is a single command: |
| 132 | + |
| 133 | +```bash |
| 134 | +pnpm check |
| 135 | +``` |
| 136 | + |
| 137 | +It runs: |
| 138 | + |
| 139 | +1. `pnpm type` — tsgo strict type-check. |
| 140 | +2. `pnpm lint` — oxlint across the tree. |
| 141 | +3. `pnpm test` — vitest (both configs). |
| 142 | +4. Format verification. |
| 143 | + |
| 144 | +Every step runs independently too: |
| 145 | + |
| 146 | +| Command | What | |
| 147 | +|---|---| |
| 148 | +| `pnpm build` | Compile `src/` → `dist/` (esbuild). `pnpm build --watch` for dev. | |
| 149 | +| `pnpm type` | Strict TypeScript check, no emit. | |
| 150 | +| `pnpm lint` | oxlint. | |
| 151 | +| `pnpm fix` | Auto-fix what's auto-fixable (formatter + lint autofixes). | |
| 152 | +| `pnpm test` | Run vitest. `pnpm test:unit path/to/file.test.mts` for a single file. Never use `--` before test paths — runs ALL tests. | |
| 153 | +| `pnpm testu` | Update vitest snapshots (review the diff before committing). | |
| 154 | +| `pnpm cover` | Coverage. Must stay at 100%. | |
| 155 | +| `pnpm format` | Run oxfmt across the tree (writes fixes). | |
| 156 | +| `pnpm format --check` | Verify formatting without writing. | |
| 157 | +| `pnpm security` | AgentShield + zizmor security scan. | |
| 158 | + |
| 159 | +### Coverage — the 100% rule |
| 160 | + |
| 161 | +`pnpm cover` must report 100% line/branch/function coverage. |
| 162 | + |
| 163 | +If you add a code path, add a test that exercises it. If you |
| 164 | +discover a corner case while reading, add a test for it even if the |
| 165 | +coverage number already says 100% (percentages can miss edge |
| 166 | +cases). |
| 167 | + |
| 168 | +When coverage drops, the failure names the file and the missed |
| 169 | +line/branch. Add a test or document an `/* c8 ignore next */` with |
| 170 | +a justification comment. |
| 171 | + |
| 172 | +### Snapshot tests |
| 173 | + |
| 174 | +`pnpm testu` regenerates snapshots. Never run this unless the |
| 175 | +snapshot drift is intentional — otherwise you are deleting future- |
| 176 | +you's safety net. Review the snapshot diff before committing. |
| 177 | + |
| 178 | +## The parallel-session git rules |
| 179 | + |
| 180 | +Forbidden in the primary checkout (the one another session may be |
| 181 | +editing): |
| 182 | + |
| 183 | +- `git stash` / `git stash pop` — shared store; another session |
| 184 | + can pop yours. |
| 185 | +- `git add -A` / `git add .` — sweeps files belonging to other |
| 186 | + sessions. |
| 187 | +- `git checkout <branch>` / `git switch <branch>` — yanks the |
| 188 | + working tree. |
| 189 | +- `git reset --hard` against a non-HEAD ref — discards another |
| 190 | + session's commits. |
| 191 | + |
| 192 | +Required for branch work: |
| 193 | + |
| 194 | +```bash |
| 195 | +git worktree add -b <task-branch> ../<repo>-<task> main |
| 196 | +cd ../<repo>-<task> |
| 197 | +# edit, commit, push from here |
| 198 | +cd - |
| 199 | +git worktree remove ../<repo>-<task> |
| 200 | +``` |
| 201 | + |
| 202 | +Required for staging: |
| 203 | + |
| 204 | +```bash |
| 205 | +git add <specific-file> [<file>…] |
| 206 | +``` |
| 207 | + |
| 208 | +Never `-A` / `.`. |
| 209 | + |
| 210 | +For a quick WIP save: commit on a new branch from inside a |
| 211 | +worktree, not a stash. |
| 212 | + |
| 213 | +## Commit messages |
| 214 | + |
| 215 | +[Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/): |
| 216 | + |
| 217 | +``` |
| 218 | +<type>(<scope>): <description> |
| 219 | +
|
| 220 | +<optional body> |
| 221 | +
|
| 222 | +<optional footer> |
| 223 | +``` |
| 224 | + |
| 225 | +Types used in this repo: |
| 226 | + |
| 227 | +- `feat` — new feature |
| 228 | +- `fix` — bug fix |
| 229 | +- `refactor` — restructure without behavior change |
| 230 | +- `docs` — documentation only |
| 231 | +- `test` — tests only |
| 232 | +- `chore` — tooling, deps, repo config |
| 233 | +- `style` — formatting / whitespace |
| 234 | + |
| 235 | +Scopes are free-form but stable: `purl-types/npm`, `url-converter`, |
| 236 | +`tour`, `hardening`. One commit = one concern. If the body |
| 237 | +explains both a refactor and a fix, they should probably be two |
| 238 | +commits. |
| 239 | + |
| 240 | +**Never add AI attribution** ("Co-authored-by: Claude", etc.) to |
| 241 | +commit messages. Humans commit; tools assist. |
| 242 | + |
| 243 | +## Opening a PR |
| 244 | + |
| 245 | +After `pnpm check` passes and all commits are conventional: |
| 246 | + |
| 247 | +```bash |
| 248 | +gh pr create --title "<conventional title>" --body "…" |
| 249 | +``` |
| 250 | + |
| 251 | +The PR body should: |
| 252 | + |
| 253 | +- Summarize the change (1–3 bullets). |
| 254 | +- Include a test plan as a markdown checklist. |
| 255 | +- Link any related issue (`Fixes #123` if applicable). |
| 256 | + |
| 257 | +CI will run: |
| 258 | + |
| 259 | +- `.github/workflows/ci.yml` — full `pnpm check` in a clean env. |
| 260 | +- `.github/workflows/pages.yml` — if you touched tour sources, |
| 261 | + rebuilds the tour and deploys to GH Pages on merge to main. |
| 262 | +- `.github/workflows/valtown.yml` — if you touched `val/`, |
| 263 | + deploys the comment backend after merge. |
| 264 | +- `.github/workflows/provenance.yml` — on tag, signs + publishes |
| 265 | + to npm with attestations. |
| 266 | + |
| 267 | +## Pre-PR checklist |
| 268 | + |
| 269 | +Copy into your PR description and tick off: |
| 270 | + |
| 271 | +- [ ] `pnpm check` passes locally. |
| 272 | +- [ ] `pnpm cover` still at 100%. |
| 273 | +- [ ] New tests for new behavior (functional style, public API). |
| 274 | +- [ ] No `null`, no `fetch()`, no `fs.rm`/`unlink`/`rmdir` direct |
| 275 | + calls. |
| 276 | +- [ ] Commit messages are Conventional, no AI attribution. |
| 277 | +- [ ] Documentation touched if user-facing behavior or API changed. |
| 278 | +- [ ] For new ecosystem handlers: entry in `src/purl-types/`, |
| 279 | + registered in `src/purl-type.ts`, builder factory in |
| 280 | + `src/package-url-builder.ts`, tests. |
| 281 | +- [ ] `CLAUDE.md` + `docs/*` updated if conventions shifted. |
| 282 | + |
| 283 | +## Hazards |
| 284 | + |
| 285 | +Things that have caught contributors before: |
| 286 | + |
| 287 | +- **Forgetting to bump `upstream/meander`** when the pin is stale. |
| 288 | + `pnpm tour:build --refresh` will re-clone and rebuild. |
| 289 | +- **Running `pnpm testu` by accident** — if you only wanted to |
| 290 | + run tests, use `pnpm test`. `testu` updates snapshots. |
| 291 | +- **Editing `dist/` directly** — it's a build artifact. Changes |
| 292 | + here are lost on next build. Edit `src/` and rebuild. |
| 293 | +- **`pnpm install` side-effects** — the install step runs `husky` |
| 294 | + to set up git hooks. A bare `npm install` in this repo will skip |
| 295 | + that and PRs will push with unformatted files. Use pnpm. |
| 296 | +- **`.env.local` accidentally committed** — it's gitignored; don't |
| 297 | + `git add -A`. |
| 298 | +- **`minimumReleaseAge` exclusions** — NEVER add packages to |
| 299 | + `minimumReleaseAgeExclude` in CI. Locally, ask before adding. |
| 300 | + The age threshold is a security control. |
| 301 | + |
| 302 | +## Further reading |
| 303 | + |
| 304 | +- [`CLAUDE.md`](../CLAUDE.md) — project-wide conventions, error |
| 305 | + message doctrine, safe-deletion rule, parallel-sessions rules. |
| 306 | +- [`docs/architecture.md`](./architecture.md) — how `src/` fits |
| 307 | + together. |
| 308 | +- [`docs/tour.md`](./tour.md) — the build pipeline that ships |
| 309 | + these docs as HTML. |
| 310 | +- [`docs/release.md`](./release.md) — what happens when we tag. |
| 311 | +- [`package.json`](../package.json) — every script spelled out. |
0 commit comments