Skip to content

Release Process

Luke Karrys edited this page Sep 23, 2022 · 202 revisions


  • The release cadence is optimized for consistency – new releases happen weekly, on the same day of the week (Wednesday)
  • Out-of-band releases can & do happen to address critical security bugs or patch a broken release & we sometimes elect to skip weeks where there is competing priorities or vacation/OOO
  • There is only one officially supported release branch: npm@8 ("latest") (ie. the latest/stable version of npm)
    • Our team does not support any LTS releases - you can read more about the versions of npm we support here.

Note: We're currently shipping weekly releases of npm@latest every Wednesday (& occasionally even more than once a week).


Each release has a release manager, who is responsible for landing that release's pull requests and dependency upgrades, ensuring that the tests pass for the release branch, writing the release notes, and running through the actual release process (see below). The current release managers are the npm CLI team:

Setting up to do releases

If you're a new release manager, you need a GPG key to sign releases with. We've been using to store/verify our public keys.

In order to sign the release tags, you will need to set this also: npm config set sign-git-tag true

Release Please

Much of the release process is automated using a customized release-please script in @npmcli/template-oss.

The configuration for release-please is located in a root repo config file release-please-config.json.

Major Releases and Prereleases

When creating a new major release, first set the prerelease: true flag in the release-please config, and then continue to use conventional commits to indicate changes and cut a branch for the latest version of the previous major.

  1. Cut a v<CURRENT_MAJOR> branch

    git checkout latest
    git checkout -b v<CURRENT_MAJOR>
    git push -u origin v<CURRENT_MAJOR>
    git checkout latest
  2. Set prerelease: true for all packages in release-please-config.json

    // ./release-please-config.json
      "packages": {
        ".": {
          "prerelease": true
  3. Commit prerelease: true as chore:

    git checkout -b prerelease
    git commit -am "chore: set prerelease flag"
    gh pr create -f
  4. Continue to use conventional commits and feature PRs

    Everything is now identical to normal release development. The only difference is that release-please will version and tag all releases with a -pre.<PRE_ID> suffix.

    The usual release process should be followed, except for the sections marked NOT FOR PRERELEASE.

    git checkout latest
    git checkout -b me/my-breaking-feature
    git commit -m 'feat: break stuff
    BREAKING CHANGE: this message gets added to the changelog'
    gh pr create -f
  5. One month later…, remove prerelease flag and commit

    Warning: All breaking changes must be merged AND released before this step

    # Remove `prerelease` flag from
    git checkout -b its-go-time
    git commit -am "chore: remove prerelease flag"
    # `release-please` uses the convention of an empty commit
    # to force all packages to be released
    git commit -m "feat: trigger release process" --allow-empty
    gh pr create -f

    This will trigger a new release without a -pre.<PRE_ID> suffix that can be merged and published normally.

Weekly Tasks

Merging Pull Requests

  1. Search for PRs to merge

    Some helpful gh searches:

    gh pr list --draft=false --search "status:pending"
    gh pr list --draft=false --search "status:success"
    gh pr list --draft=false --search "review:required"
    gh pr list --draft=false --search "review:approved"
  2. Make sure the PR is targeting latest and not something else

    gh pr view --json baseRefName -q '.baseRefName'
  3. Merge the pull request using squash or rebase

    Note: Prefer using rebase if the PR has multiple conventional commits.


    gh pr merge -s <pr-num>


    gh pr merge -r <pr-num>

Updating Dependencies

Note: Workspace do not need to be updated, since the automated release process will sync all workspace versions as part of the release pull request.

Dependency updates should all go through pull requests using the deps: conventional commit prefix. This section shows how to check for any outdated deps and make pull requests for them.

  1. Check all top level deps

    node . outdated
  2. Check all levels of production deps

    node . outdated -a --omit=dev
  3. Install and commit each dep to a PR

    git checkout -b deps/updates
    node . install <pkg>@<version>
    node . run dependencies
    # Use prefix `chore:` for dev deps
    git commit -am 'deps: <pkg>@<version>'
    git push origin deps/updates
    gh pr create -f
  4. Merge the PR if things look ok

Updating @npmcli/template-oss

Note: @npmcli/template-oss is a dependency of all workspaces and must be installed with --save-exact

  1. Update @npmcli/template-oss in all workspaces and root

    node . i @npmcli/template-oss@latest -ws -iwr --save-exact
  2. Commit and open PR

    git checkout -b deps/template-oss
    git commit -am 'chore(deps): @npmcli/template-oss@<version>'
    git push origin deps/template-oss
    gh pr create -f

Release Day Tasks

Update any last minute deps

See Updating Dependencies for the process.

Release the CLI and workspaces

Note: A single release pull request is created for the CLI and all dependent workspaces by release-please.

The release pull request is created automatically by release-please whenever a commit is pushed to the default branch. This PR should be left open until Release Day, when it should be checked to make sure all workflows are passing. You should checkout the branch locally and publish from there. Once everything is published the PR can be merged into latest.

  1. Checkout the release branch

    Ensure git status is not dirty on this branch after resetting deps. If it is, then something is probably wrong with the automated release process.

    gh pr checkout <PR-NUMBER> --force
    node . run resetdeps
    node scripts/git-dirty.js
  2. Run tests locally

    These have also been running in CI continuously each time the release PR was updated, but it is nice to confirm they pass locally since that is where we will be publishing from.

    node . run lint-all
    node . run test-all
    node scripts/git-dirty.js
  3. Check CI status

    gh pr checks --watch
  4. Publish workspaces

    Note: If we are currently publishing prereleases, make sure to publish with the prerelease tag.

    node . publish -w <WS-PKG-N>
  5. Publish the CLI

    Note: This will publish with the dist-tag set to next-<MAJOR>

    make publish
  6. Set dist-tag to published version

    Warning: NOT FOR PRERELEASE: Do not run this step for prereleases

    Note: You can optionally install via the version to see how it runs locally before tagging.

    Optionally install via version:

    npm i -g npm@<X.Y.Z>

    Set version to latest dist tag:

    node . dist-tag add npm@<X.Y.Z> latest
  7. Merge release PR

    gh pr merge --rebase
    git checkout <BASE-BRANCH>
    git reset --hard origin/<BASE-BRANCH>
    node . run resetdepts
  8. Check For Release Tags

    Release Please will run on the just pushed release commit and create GitHub releases and tags for each package.

    gh run watch `gh run list -w release -L 1 --json databaseId -q ".[0].databaseId"`
  9. Post on Twitter

    New @npmjs release: <X.Y.Z>!
    Release details...
  10. Open nodejs/node PR to update npm to latest

    Warning: NOT FOR PRERELEASE: Do not run this step for prereleases

    Sync our npm/node fork with the nodejs/node upstream:

    gh repo sync npm/node --source nodejs/node --force

    Trigger the Create CLI Deps PR action:

    gh workflow run create-cli-deps-pr.yml