# Contributing to @peculiar/utils Thank you for your interest in contributing to `@peculiar/utils`! This document describes how to set up the project, which checks a change must pass, and how releases are made. ## Development Setup ### Prerequisites - Node.js `^20.19.0 || >=22.12.0` (CI uses Node.js 24) - npm (the repository is locked with `package-lock.json`) ### Installation ```bash git clone https://github.com/PeculiarVentures/pvtsutils.git cd pvtsutils npm ci npm test npm run build ``` ## Project Layout - `src/index.ts` is the root entry point and must stay aligned with the package exports. - `src/bytes/` contains `BufferSource` and `ArrayBuffer` helpers (`./bytes`). - `src/encoding/` contains hex, binary, UTF-8, UTF-16, base64, and base64url helpers (`./encoding`). - `src/converters/` contains the converter registry and default adapters (`./converters`). - `src/pem/` contains PEM parsing and formatting helpers (`./pem`). - `src/legacy/` keeps the compatibility layer for older `pvtsutils` APIs (`./legacy`). - `build/` is generated by `npm run build` and is not committed. ## Development Workflow ### Code Quality Before opening a pull request, run: ```bash npm run lint # oxlint npm run format:check # oxfmt npm run typecheck # tsc --noEmit npm test # vitest, runs once and exits npm run build # tsdown, also checks the package with attw ``` Fix lint and formatting issues with `npm run lint:fix` and `npm run format`. Use `npm run coverage` to produce a coverage report. ### Testing - Use Vitest. - Put tests next to the code they cover as `*.spec.ts` (module tests in `src//`, public-API tests in `src/index.spec.ts`). - Cover new behavior and bug fixes with tests. ### Build And Package Exports The package is ESM-only; there is no CommonJS build, and CommonJS consumers load it through `require(esm)`. tsdown builds one entry per public subpath into `build/.js` with `.d.ts` declarations. Output is unbundled, so every source module is emitted under `build/` mirroring `src/`, but only the entry files are reachable through `exports`. The syntax target comes from `engines.node`; do not set `target` in `tsdown.config.ts`. `exports` and the legacy `main`/`module`/`types` fields in `package.json` are generated by tsdown on every build and must not be edited by hand. To add or rename a public subpath, change `entry` in `tsdown.config.ts`, run `npm run build`, and commit the regenerated `package.json`. CI fails if the build changes `package.json`. Encoding helpers are published only through `./encoding` (and the root entry) as namespaces; do not add per-file `./encoding/*` subpaths. Use named exports only (no `export default`) and no top-level `await`, so `require()` of the package keeps working on supported Node.js versions. ### Commit Messages Use [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/). Pull requests are squash-merged, so the pull request title becomes the commit message and must follow the same format. ```plain type(scope): description [optional body] [optional footer] ``` Types: `feat`, `fix`, `perf`, `refactor`, `docs`, `test`, `build`, `ci`, `chore`, `style`, `revert`. Mark breaking changes with `!` after the type or scope (for example `feat(pem)!: ...`) and/or a `BREAKING CHANGE:` footer. ### Pull Requests 1. Fork the repository and create a branch from `master`. 2. Make your changes, with tests. 3. Run the checks listed above. 4. Open a pull request with a Conventional Commit title. ## Code Style - Write TypeScript. - Follow the oxlint (`.oxlintrc.json`) and oxfmt (`.oxfmtrc.json`) configuration. Keep the lint config practical: prefer fixing the source over adding rules that only create churn, and disable a rule only when it fights the established style. - Keep changes small and localized to the owning module. - Prefer `BufferSource`-based public APIs over Node.js-specific `Buffer` types. - Document every public exported type, function, method, and exported object member with concise TSDoc. - Keep public exports stable; breaking changes need a major release. ## Releases Releases are managed by [release-please](https://github.com/googleapis/release-please) (`release-please-config.json`, `.release-please-manifest.json`): 1. On every push to `master`, the **Publish** workflow first runs lint, format, typecheck, tests, and build (the **Test** workflow). Only when they pass does release-please open or update a `chore(release): vX.Y.Z` pull request that bumps `package.json`/`package-lock.json`. The version comes from the commits: a breaking change bumps major, `feat` bumps minor, and `fix`, `perf`, `build`, and `revert` bump patch. Other types do not trigger a release. 2. Merge the release pull request. release-please creates the `vX.Y.Z` tag and a GitHub Release with the notes from the pull request; the workflow then publishes that commit to npm via Trusted Publisher, with provenance, and moves the major tag (for example `v4`) when this is the newest release of that major. To retry a failed npm publish, run **Publish** manually from the `vX.Y.Z` tag. Set the optional `RELEASE_TOKEN` secret (PAT or GitHub App token) so the Test workflow runs on the release pull request. Release notes live only in [GitHub Releases](https://github.com/PeculiarVentures/pvtsutils/releases); there is no `CHANGELOG.md`. Only `feat`, `fix`, `perf`, `build`, `revert`, and breaking changes are listed. Force a version with a `Release-As: x.y.z` line in a commit body. To fix the notes for a merged change, add a `BEGIN_COMMIT_OVERRIDE` … `END_COMMIT_OVERRIDE` block with the corrected commit message to that pull request's description; edits to the release pull request description are overwritten when new commits land. ## License By contributing to this project, you agree that your contributions will be licensed under the MIT License.