Skip to content

Local Package Versions

This is an addendum to the Getting Started guide.

When some of the packages developed in your monorepo are installed as dependencies in other packages developed in your monorepo, syncpack can check that the versions used are valid and consistent.

Target Problem

A Node.js client to communicate with your HTTP API is developed in your repo:

packages/node-client/package.json
{
"name": "@your-repo/node-client",
"version": "1.2.1"
}

As-is a front-end application which depends on it:

apps/dashboard-ui/package.json
{
"name": "dashboard-ui",
"version": "0.3.1",
"dependencies": {
"@your-repo/node-client": "1.1.4",
"@your-repo/node-client-plugin-retry": "1.7.1"
}
}

And a plugin which extends it:

packages/node-client-plugin-retry/package.json
{
"name": "@your-repo/node-client-plugin-retry",
"version": "1.7.1",
"devDependencies": {
"@your-repo/node-client": "workspace:*"
},
"peerDependencies": {
"@your-repo/node-client": "^1.0.0"
}
}

The Developers of this project:

  1. Provide a Web Service which their customers interact with via their Node.js Client and various Plugins.
  2. The Plugins are developed to be compatible with any version of the Node.js Client in the entire 1.x range, as defined in their peerDependencies. Companies around the world depend on this Client and Plugin and there is no reason to impose that all of them must use the same exact version of the Client.
  3. Use the pnpm workspace:* protocol to ensure that the local workspace package of the Client is resolved when working locally on the Plugin.
  4. Want exact versions of the Client and its Retry Plugin when running their Dashboard UI in production.

The local dependency type

The “local” dependencyType relates to the version properties of the package.json files from your own packages being developed in your monorepo.

When local is enabled, syncpack can see that:

  1. ❌ The UI is not using the latest client developed locally (1.2.1).
  2. ✅ The UI is using the latest plugin developed locally (1.7.1).
  3. workspace:* is not identical to 1.2.1 (see below).
  4. ^1.0.0 is not identical to 1.2.1 (see below).

Zero Configuration?

As a sensible default, syncpack defines a monorepo-wide exact version policy as a starting point which can be tuned from there. If your project uses exact versions everywhere, and you always want them to be identical, you will not need to define any configuration.

But most projects are not like that, and we will need to use configuration for syncpack to know what our requirements are.

Possible Solutions

Let’s look at workspace:* under devDependencies first and decide on a rule for that:

Option 1: Pin local versions to `workspace:*`

Add a Pinned Version Group so that local packages are always installed using workspace:* when they are used in devDependencies.

  • An optional label can be added to document the rule.
  • The dependencies array defines the names of the dependencies we want to target.
  • dependencyTypes results in these dependencies only being targeted by this group when they are located in devDependencies.
  • pinVersion states that these dependencies must always use workspace:*.
.syncpackrc
{
"versionGroups": [
{
"label": "Use workspace protocol when developing local packages",
"dependencies": ["@your-repo/node-client-plugin-retry", "@your-repo/node-client", "dashboard-ui"],
"dependencyTypes": ["dev"],
"pinVersion": "workspace:*"
}
]
}

The fix for the Peer Dependency of ^1.0.0 can also be used to fix the use of workspace:*. Since the current version of 1.2.1 of the Client is satisfied by both of these ranges, a Same Range Version Group can be used.

The versions will be considered a match unless eg. 2.0.0 of the Client is released, or one of its dependents uses a range which does not include its current version.

Option 2: Check that a dependency's semver ranges always match

Add a Same Range Version Group which allows local packages installed in devDependencies or peerDependencies to use different semver ranges, as long as they all match the local package version.

  • An optional label can be added to document the rule.
  • The dependencies array defines the names of the dependencies we want to target.
  • dependencyTypes results in these dependencies only being targeted by this group when they are located in devDependencies or peerDependencies.
  • The policy of sameRange states that these dependencies are considered valid if every range matches the others.
.syncpackrc
{
"versionGroups": [
{
"label": "Ensure semver ranges for locally developed packages satisfy the local version",
"dependencies": ["@your-repo/node-client-plugin-retry", "@your-repo/node-client", "dashboard-ui"],
"dependencyTypes": ["dev", "peer"],
"policy": "sameRange"
}
]
}