A good starting config for using Renovate with Node/NPM

Darragh O Riordan
6 min readAug 15, 2023
Photo by Paul Esch-Laurent on Unsplash

Renovate is a great tool for keeping your dependencies up to date. The configuration can be a bit daunting at first, so I wanted to share my default starting configuration for Nodejs projects.

If you want to jump directly to the config, open base.json on GitHub. I explain all the settings from that file in this article.

What is Renovate?

Renovate is a tool that will automatically analyse your project for dependencies and create branches, commits or PRs based on a set of custom rules for fixing them.

The pull requests that Renovate creates for you to review and merge are highly detailed and include a lot of information about the changes it is making. You get a full list of updates and it extracts the changelog for the range you are updating from the source repository and includes it in the PR.

Renovate can also automatically merge updates you choose for you.

Running Renovate

I run renovate on GitHub for CI/CD, so the github action that I run renovate with will only apply to GitHub. But the renovate.json should run on any repository provider, you may have to specify which one and read the Renovate documentation to ensure all features are supported on your CI/CD platform.

To run renovate on your GitHub project you will need

  1. A GitHub action to run renovate
  2. A token to allow renovate to make changes to your repository
  3. A renovate.json file to tell it what to do

GitHub Action

I use the following GitHub action in all my projects. You can check https://github.com/darraghoriordan/use-miller/blob/main/.github/workflows/rennovate.yaml for any updates.

This schedules renovate to run a few times throughout the day in AEST timezone. It also allows you to manually trigger renovate if you want to run it immediately. You can also override the log level or scheduling during a manual run if you want to.

name: Renovate
on:
# Allows manual/automated ad-hoc trigger
workflow_dispatch:
inputs:
logLevel:
description: 'Override default log level'
required: false
default: 'info'
type: string
overrideSchedule:
description: 'Override all schedules'
required: false
default: 'false'
type: string
# Run a few times through the day to catch up with any decisions made in the issue
# Run at odd times to avoid overlap with too many other workflows on GitHub Actions
# note github actions cron is in UTC so this is 7:18am, 12:18pm, 15:18pm in AEST.
schedule:
- cron: '18 21,2,5 * * *'
concurrency: renovate
jobs:
renovate:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3
- name: Self-hosted Renovate
uses: renovatebot/github-action@23a02fe7be9e93f857a953cc8162e57d2c8401ef # v39.0.1
env:
RENOVATE_REPOSITORIES: ${{ github.repository }}
RENOVATE_PLATFORM_COMMIT: 'true'
# Override schedule if set
RENOVATE_FORCE: ${{ github.event.inputs.overrideSchedule == 'true' && '{''schedule'':null}' || '' }}
LOG_LEVEL: ${{ inputs.logLevel || 'info' }}
with:
token: ${{ secrets.RENOVATE_TOKEN }}

The Token

Note the RENOVATE_TOKEN secret is a "classic" GitHub personal access token with repo, workspace and user:read scopes. You can create one at https://www.github.com/settings/tokens/new.

Save this as a secret in the repository that you want to renovate.

The renovate.json file

I store the actual renovate file in https://github.com/darraghoriordan/renovate-config and I extend that for each of my repositories. This makes it much easier to change the settings later.

In a target repository the extended configuration looks like

Make sure to set the “baseBranches” to the default branch of your repository.

You can see that this extends the base configuration from the config repository. The contents of that file is 150 lines long so I won't paste it here, but you can see it at https://github.com/darraghoriordan/renovate-config/blob/main/base.json.

The base configuration

I will walk through the settings I chose and explain what they do.

config:recommended

  • turns on the dependency dashboard feature so you can see a summary of status in a github issue in your repo
  • turns on semantic commit message prefixes
  • ignores node_modules and caches and things like that
  • groups well known monorepo things together such as @typescript-eslint/** - these packages are usually updated together
  • adds all replacement and workarounds. Replacements are for deprecated packages, workarounds are for little features that make it easier to work with packages and tools.

autodetectPinVersions

This tries to pin version of libraries for applications. If you are building a library you probably don’t want this. Renovate will try to detect if you are building a library and turn this off automatically.

automergePatch

This automerges patch level updates

label(renovate)

Adds a label to the PRs that renovate creates. This makes it easy to find them and filter them in the GitHub UI.

prConcurrentLimit10, prImmediately, rebaseStalePrs

These limit the amount of concurrent PRs, tell renovate to create PRs immediately and to rebase stale PRs. When you merge one set of for say, patches, there might be conflicts in the package-lock file. This will rebase the PRs to fix those conflicts.

:separatePatchReleases

This ensures that patch releases get placed on their own branches. This is useful if you want to merge them immediately.

:timezone(Australia/Melbourne)

Set your time zone if you’re scheduling anything!

helpers:disableTypesNodeMajor

This prevents renovate from suggesting major version updates for @types/node. This is because you should always use the types for the version of node you are using.

helpers:pinGitHubActionDigests

This pins the digest of GitHub actions. This is useful because GitHub actions are often loosely versioned, so if you don’t pin the digest you could get a breaking change in your CI/CD pipeline.

npm:unpublishSafe

This has renovate wait 3 days after an NPM package is released before updating to it. This is useful because sometimes packages are released with bugs and then unpublished. This prevents renovate from updating to those packages.

preview:dockerCompose, preview:dockerVersions

These two configs are for docker updates. They are not as stable as the package json updates but they have worked well enough in my experience.

"baseBranches": ["master"],

This tells renovate what the default branch is. This is important because it will create PRs against this branch. It’s best to override this in the target repository.

"rebaseWhen": "conflicted",

Compliments the stale PRs setting above. This tells renovate to rebase PRs when they are conflicted.

"labels": ["dependencies"],

Adds a label to every PR

"rangeStrategy": "bump",

Tells renovate to explicitly bump the version in a range, even if the existing range satisfies the newer version. e.g. ^1.0.0 -> ^1.0.1 instead of leaving ^1.0.0 -> ^1.0.0 and only bumping the package lock.

"postUpdateOptions": ["npmDedupe", "pnpmDedupe", "yarnDedupeFewer"],

Runs the relevant package manager dedupe command after updating a package lock file

"transitiveRemediation": true,

Tries to update all vulnerable dependencies in your package tree

"separateMajorMinor": true,

Create separate branches for major and minor updates

  "lockFileMaintenance": {
"enabled": true,
"automerge": true
},

Automatically update the lock files if there are any vulnerabilities. This is relatively safe because they should all be within the version range specifications so we auto-merge any fixes.

The custom package rules

I have added a description for most of the custom package rules. I will summarize here.

The package rules group all major version changes together, all minor changes together and all patches together. This greatly reduces the amount of PRs to deal with. e.g.

    {
"description": "Group patch updates together and automerge if tests pass",
"matchPackagePatterns": ["*"],
"matchUpdateTypes": ["patch"],
"addLabels": ["patch"],
"commitMessagePrefix": "fix(renovate): patch",
"groupName": "patch",
"automerge": true
},

I auto-merge patches above. I also auto-merge updates to definitely typed packages because they are usually safe.

Updates to linting and prettier are auto-merged. Updates to github actions are also auto-merged.

Updates to docker file alpine images are bumped for minor versions only.

NodeJS, PNPM and TypeScript are all given their own special branches for major updates because these usually break things.

Postgres and Mongo major versions are never updated.

Potential schedule improvements for your organisation

The current configuration will run every day!

You might want to add a schedule to the overall configuration e..g run once per week. You can also add a schedule for individual package rules. e.g. you might want to update your linting rules once per day but your dependencies once per week.

Note that there is the cron for the GitHub action. This just wakes renovate up so it can rebase PRs, create PRs and other work like that. There are schedules you can set in the renovate.json file for renovate itself. For example you might only want to update eslint once per month. Renovate gives you that granularity.

Those renovate schedules to reduce noise for your team is what i’m referring to here. See https://docs.renovatebot.com/key-concepts/scheduling/ for more details.

Conclusion

That’s it! This should be a good start for any typescript or javascript based project. You will likely want to copy or fork the base and change it to suit your project.

I hope this helps you get started with renovate!

Originally published at https://www.darraghoriordan.com.

--

--

Darragh O Riordan

I live and work in Sydney, Australia enjoying the mountains and the ocean. I build and support happy teams that create high quality software for the web!