We're glad you want to contribute to Reliability Kit!
- Requirements
- Getting set up
- Creating a new package
- Installing dependencies
- Testing
- Committing
- Releasing
- Issue management
In order to contribute and make changes to this repository, you'll need some software installed:
-
Node.js on a version defined in
package.json
:engines.node
-
npm on a version defined in
package.json
:engines.npm
(usually bundled with Node.js)
The following software will make your life easier when making changes to Reliability Kit:
- Volta can manage your Node.js and npm versions automatically
To set up this repo to make changes locally, make sure you have all of the required software, then:
- Clone this repo and
cd
into it - Run
npm install
to install dependencies - Run
npm test
to verify that everything's working
To create a new package in this repo, run the following command. This will bootstrap the package files and make sure it's added to the release configuration and is auto-versioned correctly.
The name of the package must be lowercase with words hyphen-delimited.
npm run create-package <NAME>
You'll also need to manually add an entry for the package to the Dependabot config so that dependency update pull requests can be opened.
It's important that you run npm install
before committing. This ensures that the new package is registered in the main package-lock.json
.
You'll need to manually add the package to the list of packages in the README once it's ready to be used by other teams.
Important
Normally we want the first release of a new package to be a pre-release (e.g. 0.1.0
). To do this, you'll need to hard-code the release version in a commit.
Note
Wherever the package version appears, you should leave it as 0.0.0
to allow Release Please to correctly bump and release the first version.
Because we use a monorepo managed with npm workspaces, installing dependencies is slightly different to other projects. You'll need to adjust the way you install dependencies as follows. If you're reviewing a pull request on Reliability Kit, then please also check that dependencies have been installed in the expected places.
The top level package.json
file should never include any dependencies apart from devDependencies
. If you install the dependencies that individual packages rely on here then they will not have access to them after publishing.
If you need a new development dependency that is used across the whole repository, then you can install it as normal by running the following from the repo base path:
npm install --save-dev <DEPENDENCY_NAME>
If a specific package relies on a new dependency, then you must not cd
into the package and run an npm install
. This will end up creating a package-lock.json
file within that folder and cause all sorts of dependency issues. Instead, you must use the --workspace
flag, setting it to the package you want to install a dependency in:
npm install --workspace=packages/<PACKAGE_NAME> <DEPENDENCY_NAME>
This ensures that we continue to maintain a single package-lock.json
file in the root of the repo.
Some packages may also require their own specific development dependencies, for example @types
packages or specific modules required for testing that package alone. This can be done with the --workspace
flag too:
npm install --save-dev --workspace=packages/<PACKAGE_NAME> <DEPENDENCY_NAME>
If a package within Reliability Kit relies on another Reliability Kit package, you'll still need to codify this relationship in the package.json
file for the dependent package. You can do this normally with an npm install
, using the full @dotcom-reliability-kit/<NAME>
package name. For example if your package relies on the internal errors
package then you'd run this:
npm install --workspace=packages/<PACKAGE_NAME> @dotcom-reliability-kit/errors
Reliability Kit includes some tooling to ensure that code quality and consistency is high.
Reliability Kit aims to support all versions of Node.js which are in the "Active LTS" or "Maintenance" release phase. We run all linting and tests on all the versions of Node.js that we support and we document the supported versions in the package.json
engines.node
field for the project and for each individual package.
As well as the LTS versions of Node.js, we also try to immediately support Node.js versions in the "Current" release phase as long as they're an even major version number. We never support odd version numbered Node.js as these do not transition into LTS or Maintenance phases.
We aim to support new even Node.js versions as soon as they're released, but we need to wait until we can automate testing for that version. That relies on a new CircleCI node image being published.
Once that's done, you need to do the following:
-
Add the new Node.js version to all
package.json
files in the repo under theengines.node
property. You must add new versions with a double pipe and follow theMAJOR.x
format, e.g. append the existing engines with|| 28.x
. -
Update the CircleCI config matrix builds to add the new Node.js version to the array of versions. For this you'll need the full version number, e.g.
28.0.0
. Find all instances ofnode-version:
and update the array. -
Commit your changes under a
feat: support Node.js XXX
commit (see committing guide) and open a pull request. We consider officially supporting a new version of Node.js to be a feature-level release.
When a new version of Node.js moves from the "Current" to "Active LTS" release phase, we set that version of Node.js to be the default version for the project. You can do this by:
-
Update the CircleCI config default Node.js version to the new Active LTS version of Node.js. Find an instance of
node-version
with adefault
property and set that string to the new version. -
Update the Volta config in
package.json
so that we use the latest LTS version of Node.js when developing locally. -
Commit your changes under a
chore: switch active LTS versions
commit (see committing guide) and open a pull request. We consider switching the default version of Node.js as a chore-level commit, because it has no impact on our actual Node.js support – we still test against all other versions.
When a Node.js version reaches end-of-life and is no longer maintained, we can consider dropping support for it in Reliability Kit. This should be a discussion – just because a version is end-of-life it doesn't mean we've fully migrated away from it. When dropping support for a Node.js version in Reliability Kit you should consider all of the users we support.
If we decide to drop support for a Node.js version, e.g. we want to start using features available only in later versions, then do the following:
-
Remove the Node.js version from the
engines.node
property in allpackage.json
files in the repo. Remove the version as well as the double pipe which separates it from the next version. -
Update the CircleCI config Node.js versions in each matrix to remove the old Node.js version. Find instances of
node-version
with an array and remove the relevant items. -
Commit your changes under a
chore!: drop support for Node.js XXX
commit (see committing guide) and open a pull request. We consider removing a Node.js version to be a breaking chore because it requires changes in dependent apps – they are forced to migrate Node.js versions.
JavaScript files are expected to pass the linting rules defined by the project (ESLint and Prettier). We attempt to run the linters on every commit, but you can also check lint errors manually either by installing the VS Code ESLint extension or by running the following locally:
npm run lint
The linters are also run on pull requests and linting errors will block merging, so it's useful to check before opening a PR.
We do not write TypeScript code, but we do write thorough JSDoc and publish type declarations, then test against it which gives us all the benefits of TypeScript (more info).
We do not compile the code in our packages, but we do check that all variables are set to the correct types. If there are any type errors then you should see these in your editor if you're using VS Code. Otherwise type checking can be manually run as part of linting:
npm run lint
As with ESLint, we check types in pull requests and errors will block merging, so it's useful to check before opening a PR.
We run unit tests with Jest and aim for 100% coverage. Tests are written within each package (e.g. packages/example/test/example.spec.js
) and are run in parallel. You can run the tests with the following:
npm run test
Tests are also run on pull requests and failing tests will block merging, so it's useful to check before opening a PR.
We intentionally fail the unit tests if coverage drops below 100%. This library is meant to help our applications be more reliable and so it's important that we cover as many edge cases as possible. If you have a valid reason for some code not to be covered, e.g. an empty function as a default parameter, then use code comments to disable coverage for that line or block:
/* istanbul ignore next */
function example() { console.log('this is not covered'); }
This is better than dropping the required coverage because:
-
We have to make an active and conscious decision to drop a piece of code below 100% coverage
-
The fact that code isn't covered is documented in the code
-
It opens up discussion in the pull request when we see an uncovered piece of code – maybe the reviewer will have some useful ideas about how to make it possible to cover
We require commit messages to be written using conventional commits. This is how our automated release system knows what a given commit means.
<type>: <description>
[body]
The type
can be any of feat
, fix
, docs
or chore
.
The prefix is used to calculate the semver release level:
type | when to use | release level |
---|---|---|
feat | a feature has been added | minor |
fix | a bug has been patched | patch |
docs | a change to documentation | patch |
chore | repo maintenance and support tasks | none |
Indicate a breaking change by placing an !
between the type name and the colon, e.g.
feat!: add a breaking feature
We use commitlint to enforce standard commit messages. If you're reviewing pull requests in this project then be sure to check that all commit messages conform anyway.
Because of the way our release process works (as well as smaller focused PRs being easier to understand and review) it's important to consider the scope of the changes in your PR. When calculating the next version bump for a package, Release Please will look at all the files in the pull request rather than just the files related to the individual commits. This can cause unwanted version bumps, e.g. a docs
changes in the root of the repo can end up creating extra patch
releases of packages that aren't actually impacted by it.
This makes it important to consider whether the changes you're making outside of the packages
folder really belong in the same PR as changes to packages. If you're making broad changes to the repo this can result in unnecessary version bumps for packages if you use anything other than a chore
commit for them.
If superflous releases are generated after merging a PR, it's possible to fix it.
In order to preserve conventional commits, pull requests must be merged or rebased rather than squashed. This is enforced in the repo.
Our releases are managed by Release Please.
When a commit with the feat
or fix
commit type prefix or a breaking change (!
) reaches the main
branch, a pull request will be opened with a name matching something like chore: release main
. Merging this pull request will automatically create GitHub releases for each package as well as publishing the new package versions to npm.
If the PR is left alone, it will continue to be updated with new releases as more commits appear on the main
branch.
Before approving and merging the release PR, make sure you review it. You need to check the package versions that it updates to make sure you’re only releasing the things you expect.
Sometimes it's necessary to hard-code a release version, for example if commits have been pushed to main
which don't match our commit format. A more common reason to do this is when you want to publish a prerelease package to experiment before marking it as stable (e.g. publishing a v0.1.0
before v1.0.0
).
You can override the version that Release Please will release by adding a footer to one of the commits in a pull request:
Release-As: 0.1.0
See the Release Please documentation for more information.
Sometimes the releases that Release Please decides to create may be incorrect, because of the way it bumps packages for all changed files in a PR. Considering pull request scope is important, but sometimes it's unavoidable that some additional changes sneak in. In this case it's possible to change the releases that an already-merged PR will create.
Update the PR description with a special override to correct the release type and re-run the Release Please command locally:
npx release-please release-pr --token="XXXXXX" --repo-url="Financial-Times/dotcom-reliability-kit"
In this command, change XXXXXX
to a GitHub token with write access to the Reliability Kit repo. This can be a personal or bot token, but it's best to use the one defined in the Dotcom Reliability Kit vault as RELEASE_PLEASE_GITHUB_TOKEN
.
There's an example of a PR which we did this for here.
We use GitHub issues and a project board to track work that needs to be done on Reliability Kit.
You can track the work that we're doing in the roadmap on our project board, and feel free to open new bug reports and feature requests on the repo.
If you're managing issues, we have a suite of labels. As well as the usual bug
or enhancement
labels, we also use the following to help us categorise and prioritise work:
label | when to use |
---|---|
package: <NAME> |
Used to help us filter bugs and issues by package. Add these labels to an issue so we can see if a particular package is causing us more problems over time |
requested |
Used to indicate that a team outside of the maintainers of Reliability Kit have requested this feature, which helps us prioritise it against other work |