# Contributing to PKI.js Thank you for your interest in contributing to `pkijs`! 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 `^22.18.0 || ^24.11.0 || >=26.0.0` for development, as required by tsdown and Vitest (CI uses Node.js 24). The published package supports `^20.19.0 || >=22.12.0`. - npm (the repository is locked with `package-lock.json`) ### Installation ```bash git clone https://github.com/PeculiarVentures/PKI.js.git cd PKI.js npm ci npm test npm run build ``` ## Project Layout - `src/index.ts` is the package's only entry point and must stay aligned with the package exports. When it runs, it registers a default crypto engine built on `globalThis.crypto` (`src/CryptoEngine/CryptoEngineInit.ts`). - `src/.ts` contains one ASN.1 structure per file (`Certificate`, `SignedData`, `EnvelopedData`, `OCSPResponse`, ...), each a `PkiObject` with `fromSchema`/`toSchema`/`toJSON`. - `src/CryptoEngine/` contains the WebCrypto-based `CryptoEngine`; `src/common.ts` holds the engine registry (`setEngine`/`getEngine`/`getCrypto`) and shared helpers. - `test/` holds code shared by the specs: `test/scenarios/.ts` builds and checks the structures that `src/.example.spec.ts` tests, `test/NodeEngine/` is a Node.js crypto engine for `src/PFX.node.spec.ts`, and `test/utils.ts` has common helpers. - Runnable examples for users live on the website, in `website/docs/examples/`; see [`website/README.md`](website/README.md). - `build/` is generated by `npm run build` and is not committed. - `website/` is the [pkijs.org](https://pkijs.org) Docusaurus site, a separate npm package with its own lockfile. Its API pages are generated from `src/` with TypeDoc. The root lint, format, test and build tools ignore it; see [`website/README.md`](website/README.md) to run it. ## 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 `src/.spec.ts` (public-API tests in `src/index.spec.ts`). - The specs run against the `@peculiar/webcrypto` engine registered in `vitest.setup.ts`. - `src/CertificateChainValidationEngine.pkits.spec.ts` runs the [NIST PKITS](https://csrc.nist.gov/projects/pki-testing) path validation tests. - 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 bundles `src/index.ts` into a single `build/index.js` with a `build/index.d.ts` declaration, both with the LICENSE banner. The syntax target comes from `engines.node`; do not set `target` in `tsdown.config.ts`. The output stays bundled because importing the package registers the default crypto engine (`initCryptoEngine()` in `src/index.ts`). With a single module, bundlers keep that call whenever any export is used, so the package can still be published with `"sideEffects": false`. Don't switch to unbundled output without moving that registration. `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 change the entry point, change `entry` in `tsdown.config.ts`, run `npm run build`, and commit the regenerated `package.json`. CI fails if the build changes `package.json`. Use named exports only (no `export default`) and no top-level `await`, so `require()` of the package keeps working on supported Node.js versions. `asn1js` is a peer dependency (and a dev dependency for the tests): pkijs runs `instanceof` checks against asn1js classes on values that applications build, so both must use the same copy. The tsconfig sets `"useDefineForClassFields": false`, as in v3: a class field without an initializer is not emitted. Many structures test optional fields with `"field" in this`, which would always be true if every declared field became an own property. `vitest.config.ts` applies the same semantics to the tests. ### 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!: ...`) 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. - Keep the code free of Node.js-specific APIs; the package runs in Node.js and browsers and uses WebCrypto through the crypto engine. - Don't add import-time side effects beyond the default engine registration described above. - Document every public exported type and function with concise TSDoc. - Keep public exports and class identities stable; downstream packages (`xmldsigjs`, `@peculiar/pdf`, ...) depend on them, and 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/PKI.js/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. The website is deployed separately: the **Website** workflow builds `website/` on every pull request and publishes it to GitHub Pages on every push to `master`. ## License By contributing to this project, you agree that your contributions will be licensed under the BSD 3-Clause License.