Skip to content

Getting Started

Syncpack is a command-line tool to manage multiple package.json files.

  • Find and fix dependency version mismatches.
  • Enforce a single version policy, or create partitions with separate policies.
  • Find and bump outdated versions from the npm registry.
  • Ensure some dependencies always remain pinned at a specific version.
  • Ban some dependencies from being used: anywhere, or in specific places.
  • Define rules for where exact or loose semver ranges should be used.
  • Assign packages as the source of truth for specific dependencies’ versions.
  • Sort and format package.json files consistently.

Quick run

The quickest way to run syncpack to try it is via npx.

Terminal window
npx syncpack list

Install

It’s best to install syncpack as a dev dependency, so that everyone working on your project uses the same version.

Terminal window
npm install syncpack --save-dev

Anyone on the team can now use npm exec to access their local installation of syncpack:

Terminal window
npm exec syncpack -- list

For the rest of this guide I will run synpack as if it is installed globally.

Run

List every dependency in every package.json file in the project:

Terminal window
syncpack list

List only the contents of the dependencies and devDependencies objects:

Terminal window
syncpack list --types dev,prod

List the versions of packages developed in this project:

Terminal window
syncpack list --types local

Start Small

When setting up a project, I’d recommend that you start small and focus on only production dependencies. Monorepos are large and complex and I think it’s better to tackle them in stages.

Create the following config file at .syncpackrc in the root of your project:

.syncpackrc
{
"dependencyTypes": ["prod"]
}

Now syncpack commands will only inspect the dependencies properties of package.json files.

Audit

Now take a look at your production dependencies:

Terminal window
syncpack list

You will likely see some warnings, and each warning will display an error code to identify the reason it is invalid. Every warning is explained in the Status Codes documentation.

We’ll look at how to fix mismatching versions, but first let’s look at the semver ranges.

Semver Ranges

Examples of Semver Ranges
RangeExample
<<1.4.2
<=<=1.4.2
""1.4.2
~~1.4.2
^^1.4.2
>=>=1.4.2
>>1.4.2
**

Looking at your syncpack list output, which semver range is used in the majority of cases?

Supposing you prefer exact version numbers, you can define a policy to standardise them:

.syncpackrc
{
"dependencyTypes": ["prod"],
"semverGroups": [
{
"label": "use exact version numbers in production",
"packages": ["**"],
"dependencyTypes": ["prod"],
"dependencies": ["**"],
"range": ""
}
]
}

What we’ve created is called a Semver Group. Whenever syncpack finds an instance of a dependency, it walks through your semverGroups array in source code order until it finds a match – the first match wins and syncpack stops searching. An instance can only belong to one Semver Group (and one Version Group, which we’ll see later) and the groups an instance belongs to define the rules that form its version policy.

In our case we’ve decided that every instance under a dependencies object must always have a semver range of "" for an exact version number.

We can now check the semver ranges of our production dependencies:

Terminal window
syncpack lint-semver-ranges

…fix them

Terminal window
syncpack set-semver-ranges

…and see that they are now valid

Terminal window
syncpack lint-semver-ranges

Adding overrides

Sometimes you will have exceptions to a rule, instances which for whatever reason can’t conform to a broader policy and need special treatment. To override a Semver or Version Group, define a more specific one nearer the start of the semverGroups or versionGroups array.

For example, to use ^ in just one package we could use the following config, where dashboard-ui is the name property of the package.json file we want to make an exception for.

.syncpackrc
{
"dependencyTypes": ["prod"],
"semverGroups": [
{
"label": "use caret ranges in the dashboard-ui in production",
"packages": ["dashboard-ui"],
"dependencyTypes": ["prod"],
"dependencies": ["**"],
"range": "^"
},
{
"label": "use exact version numbers in production",
"packages": ["**"],
"dependencyTypes": ["prod"],
"dependencies": ["**"],
"range": ""
}
]
}

Now when we run syncpack lint-semver-ranges we will see that dashboard-ui is invalid because we updated out configuration to expect caret ranges and haven’t updated them yet. When we run syncpack set-semver-ranges they are fixed to have caret ranges as expected. The rest of the monorepo continues to use exact version numbers for production dependencies.

Version Mismatches

Now that we’re using consistent semver ranges under dependencies, let’s return to version mismatches.

Terminal window
syncpack list-mismatches

The most common of syncpack’s Status Codes is HighestSemverMismatch which tells us that all versions used for that dependency are valid semver, but they are not identical and the one with the highest semver version should be used by all.

If you don’t have any special requirements, they can be fixed automatically

Terminal window
syncpack fix-mismatches

If you see UnsupportedMismatch warnings, those are cases where there is no semver version (such as a reference to a file or a git hash) and syncpack cannot know what you would want to do.

You can resolve those using an interactive walkthrough:

Terminal window
syncpack prompt

Adding overrides

We saw earlier how to handle overrides for Semver Groups and Version Groups work in exactly the same way.

A group can be applied to an entire monorepo, specific instances, or anything in between. Groups can be layered over others to apply overrides as we saw in the previous section.

You should hopefully now have a good intuition for how instances get assigned to groups. All that remains is to choose groups which exhibit the behaviour you need and to apply combinations of the dependencies, dependencyTypes, and packages properties to them to target the instances you need to.

Version Groups provide a lot of really useful functionality, let’s look at them some more.

Version Groups

Version Groups create partitions where dependencies inside each group can be internally consistent, without affecting the other groups. They let you handle special cases or provide more specific rules for what to do in certain situations and are best understood by looking at some example use cases.

Incompatible packages using the same framework

A 3rd party framework has had a major update which requires breaking changes but some of your packages can’t easily be upgraded and will need to remain on an older version, while the rest are able to upgrade today.

  1. You don’t want mismatches between your packages using the latest version of the framework - they should all use eg. next@13.1.6 and react@18.2.0.
  2. You don’t want mismatches between the packages using the old version - they should all use eg. next@11.1.4 and react@17.0.2.
  3. You do want those groups to use different versions to each other and not have Syncpack make them all the same.
  4. You only have this problem with next and react, other dependencies can continue to be kept consistent with each other throughout the full monorepo.
.syncpackrc
{
"dependencyTypes": ["prod"],
"semverGroups": [
{
"label": "use caret ranges in the dashboard-ui in production",
"packages": ["dashboard-ui"],
"dependencyTypes": ["prod"],
"dependencies": ["**"],
"range": "^"
},
{
"label": "use exact version numbers in production",
"packages": ["**"],
"dependencyTypes": ["prod"],
"dependencies": ["**"],
"range": ""
}
],
"versionGroups": [
{
"label": "These packages are stuck on next@11 for now",
"packages": ["@stricken/server", "@stricken/ui"],
"dependencyTypes": ["prod"],
"dependencies": ["next", "react", "react-dom"]
}
]
}

Keep going

Update monorepo dependencies

Syncpack can also update dependencies to the latest versions from the npm registry and is best demonstrated by example.

Terminal window
# update packages in `devDependencies` in the whole repo
syncpack update --types dev
# update eslint related packages
syncpack update --filter eslint
# update `dependencies` in just one package
syncpack update --source packages/foo --types prod
# update react related packages in `dependencies` only
syncpack update --filter react --types prod

You’ll be guided through an interactive prompt to choose which updates you want to apply.

Next Steps

  • Browse the Examples to see some common use cases and how to handle them.
  • See what other functionality is provided by Version Groups Banned, Ignored, Lowest Version, Pinned, Same Range, Snapped To, and Standard .
  • Read the guide on Local Package Versions, which is an advanced topic.
  • If you like Syncpack, tell people about it. Syncpack is a single person project done in what spare time I can find, has been an absolute ton of work, and is completely free.