Skip to content

Latest commit

 

History

History
309 lines (182 loc) · 18 KB

contributing.md

File metadata and controls

309 lines (182 loc) · 18 KB

Contributing

We're glad you want to contribute to Reliability Kit!

Requirements

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)

Optional

The following software will make your life easier when making changes to Reliability Kit:

  • Volta can manage your Node.js and npm versions automatically

Getting set up

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

Creating a new package

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.

Installing dependencies

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.

Repo-wide dependencies

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>

Package dependencies

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>

Depending on other Reliability Kit packages

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

Testing

Reliability Kit includes some tooling to ensure that code quality and consistency is high.

Node.js support

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.

Supporting a new Node.js version

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:

  1. Add the new Node.js version to all package.json files in the repo under the engines.node property. You must add new versions with a double pipe and follow the MAJOR.x format, e.g. append the existing engines with || 28.x.

  2. 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 of node-version: and update the array.

  3. 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.

Switching the default LTS Node.js version

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:

  1. Update the CircleCI config default Node.js version to the new Active LTS version of Node.js. Find an instance of node-version with a default property and set that string to the new version.

  2. Update the Volta config in package.json so that we use the latest LTS version of Node.js when developing locally.

  3. 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.

Dropping support for a Node.js version

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:

  1. Remove the Node.js version from the engines.node property in all package.json files in the repo. Remove the version as well as the double pipe which separates it from the next version.

  2. 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.

  3. 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.

Linters

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.

Type safety

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.

Unit tests

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.

Coverage

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

Committing

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]

Commit type prefixes

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

Commit linting

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.

Pull request scope

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.

Merging pull requests

In order to preserve conventional commits, pull requests must be merged or rebased rather than squashed. This is enforced in the repo.

Releasing

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.

Hard-coding the release version

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.

Correcting releases

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.

Issue management

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