Library / SDK
JamieMason/syncpack avatar
JamieMason/syncpack

syncpack: finding and fixing dependency version mismatches across a JavaScript monorepo

Consistent dependency versions in large JavaScript Monorepos.

2,099 stars74 forksRustMIT

At a glance

What is it?
syncpack is a CLI that finds version mismatches in monorepo package.json files, enforces version policies, updates outdated versions from the npm registry, and auto-migrates entries to pnpm or bun catalogs. The tool is written in Rust and ships as a compiled binary despite being distributed via npm.
Who is it for?
syncpack works best in monorepos where inconsistent dependency versions cause bundling duplication or test failures in CI. It does not install packages, so every fix or update pass needs a follow-up package manager install to apply the changes to node_modules.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 15 days ago.
What is it written in?
Mainly Rust, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 6, 2026, and from our analysis. They are not legal advice.

Editorial analysis

Version drift grows with every new package in a monorepo

When a JavaScript monorepo accumulates packages, the same dependency often ends up declared at different versions across multiple package.json files. React at 17.0.2 in one package and 16.14.0 in another may each satisfy local peer dependencies, but the duplication contributes to bundle weight and inconsistent runtime behavior. syncpack's lint command scans every package.json in the workspace, collects all version declarations, and reports every dependency appearing at more than one version.

That scanning covers more than plain consistency checks. The tool can enforce a single version policy across the entire workspace, or it can configure partitions where different groups of packages follow separate policies. Partitioned policies matter in repos that span server-side packages with stricter Node.js version requirements and browser-targeted packages with different compatibility constraints: a blanket single-version rule would be too strict or too loose depending on which side you optimize for.

Syncpack can also ban specific dependencies from appearing in defined locations, pin certain packages to exact version strings, and define which semver range format is acceptable for each dependency type. Those rules live in a configuration file rather than being passed as flags on every invocation.

A Rust binary distributed through npm, optimized for size over speed

syncpack's primary language is Rust, which is unusual for a tool that targets JavaScript monorepos. Version 15.3.3 is distributed as a compiled binary wrapped in an npm package. Eight platform-specific environment files sit at the repository root: .env.darwin-arm64, .env.darwin-x64, .env.linux-arm64-glibc, .env.linux-arm64-musl, .env.linux-x64-glibc, .env.linux-x64-musl, .env.win32-arm64-msvc and .env.win32-x64-msvc, covering the major desktop and server targets.

Reading the Cargo.toml release profile reveals an explicit size-first trade-off. opt-level is set to "z", and the comment in the file reads "Optimise for size rather than speed". Most performance-focused CLI tools built in Rust optimize for execution speed. This one deliberately prioritizes a smaller binary. The same release block sets panic = "abort", labeled in the file as "Abort on panic rather than unwinding", which further reduces binary size by removing the unwind machinery. lto = true enables link-time optimization, and strip = true removes symbols from the output.

For npm registry calls, which power the update command, the Cargo.toml lists reqwest version 0.12 with the rustls-tls feature and default-features = false. That configuration uses Rust's own TLS implementation rather than the system's OpenSSL, which matters in build environments where OpenSSL is absent or pinned. Semver parsing comes from the node-semver crate at version 2.2.0, a Rust port of the original Node.js semver package. The Cargo.toml edition field is set to "2024", the latest Rust edition.

Running the first lint after npm install --save-dev syncpack

Installation adds syncpack as a development dependency:

bash
npm install --save-dev syncpack

That gives access to the syncpack command. The natural starting point is syncpack lint, which checks every package.json the tool can find. To restrict the check to only dependencies and devDependencies:

bash
syncpack lint --dependency-types prod,dev

The output names every mismatched dependency, the versions in use, and the files where each version appears. Running fix after identifying issues applies the autofixable resolutions with the same scope restriction:

bash
syncpack fix --dependency-types prod,dev

Both commands accept identical targeting flags, so a scope defined for lint transfers directly to fix. The full option list for any command is available via --help, and a shorter summary appears with -h.

lint finds all mismatches; fix resolves only the autofixable ones

The --specifier-types flag restricts a lint or fix pass to a particular version format. Passing exact limits the scope to pinned versions like "1.2.3", while range targets caret and tilde specifiers. That filtering is useful when the goal is to verify that certain dependencies are pinned without also checking range-based entries.

Dependency names accept glob patterns. Targeting only packages in the @types scope:

bash
syncpack lint --dependencies '@types/**'

The inverse syntax excludes by type: --dependency-types '!peer' checks everything except peerDependencies. Sorting output by how many times each dependency appears across the workspace:

bash
syncpack lint --sort count

Not every mismatch syncpack lint finds is autofixable. Where the correct resolution cannot be determined mechanically, lint reports the issue and fix leaves the entry alone. The gap between reported issues and autofixable issues is the manual work remaining after a fix pass, and the README does not enumerate which mismatch categories fall into each group. syncpack fix --help is the reference for the current version's behavior.

The update command checks the npm registry and preserves semver range preferences

The update command connects to the npm registry to look up the latest published versions and compares them against current entries in package.json. One difference from a manual edit is that it preserves the range format of the existing entry. An entry written as ^1.2.3 stays a caret range after the bump rather than becoming a pin or a different range style.

The --target flag controls which version increment is accepted:

bash
syncpack update --target latest
syncpack update --target minor
syncpack update --target patch

latest accepts any version increase, minor restricts to minor-version bumps, and patch allows only patch increments. For reviewing proposed changes before committing to them, the interactive mode presents each update as a prompt:

bash
syncpack update --interactive --target patch

Monorepos using pnpm catalogs can target catalog entries separately. --dependency-types pnpmCatalog updates entries in pnpm-workspace.yaml, and a specific named catalog is reachable with --dependency-types 'pnpmCatalog:react18'. The semver range preservation applies to catalog entries the same way it does to package.json entries.

pnpm and bun catalogs as a first-class migration target

pnpm catalogs and bun catalogs centralise dependency version declarations at the workspace level rather than distributing them across individual package.json files. syncpack can auto-migrate an existing monorepo into that structure. The README describes the capability as migrating "all or parts of your repo" to pnpm catalogs or bun catalogs.

After migration, the update command targets the new catalog entries the same way it targets package.json entries. --dependency-types pnpmCatalog covers all catalog entries in pnpm-workspace.yaml, and a named catalog is addressable with --dependency-types 'pnpmCatalog:react18'. Version consistency checks apply to catalog entries too, so the lint rules enforce the same policies on the centralised declarations as on per-package files.

The reverse migration path, moving entries back from a catalog to individual package.json files, is not described in the README or in the other repository files provided.

The .syncpackrc.json configuration and what the documentation leaves to syncpack.dev

A .syncpackrc.json file sits at the project's own repository root. syncpack governs its own workspace, which contains both a pnpm workspace (pnpm-workspace.yaml and pnpm-lock.yaml are present at the root) and a Cargo workspace (Cargo.toml lists crates/syncpack-specifier as a workspace member). The project applies its own version policies to itself.

The configuration format for .syncpackrc.json is not reproduced in the README. The README links to a Getting Started guide at syncpack.dev/ and a migration guide at syncpack.dev/guide/migrate-v14/ for moving from version 13 or earlier to version 14. Writing a non-trivial configuration requires the documentation site rather than the README alone.

The format command manages package.json structure separately from version policy. It sorts fields into a predictable order, alphabetises nested fields, and applies shorthand for fields like "repository" and "bugs" where the format allows it. Checking formatting without modifying any file:

bash
syncpack format --check --source 'packages/pingu/package.json'

That targets a single package. Without --source, the command checks the whole monorepo.

syncpack writes manifests but does not run the package manager

syncpack reads and modifies package.json and workspace files. It does not resolve the full dependency graph, does not run npm install or any equivalent, and does not alter the contents of node_modules. Every change that fix, update or format makes is a change to a text file. Applying those changes to the actual installation requires running the package manager's install step after syncpack finishes.

The json command outputs every dependency instance as a JSON object, one per line, for piping into external tools or custom policy scripts. The list command provides similar visibility inside the terminal, with options to show instances, statuses, hints and ignored entries:

bash
syncpack list --show all

Neither command modifies any file. Both return a non-zero exit code when issues are found, so they fit into CI pipelines that need to fail on version drift without applying any automatic change. The list command also accepts --specifier-types and --dependency-types for the same scoping available on lint and fix.

Editorial conclusion

syncpack works best in monorepos where inconsistent dependency versions cause bundling duplication or test failures in CI. It does not install packages, so every fix or update pass needs a follow-up package manager install to apply the changes to node_modules. Before adopting, run syncpack lint --dependency-types prod,dev across the full workspace and check how many mismatches are autofixable versus how many require manual resolution. Repos not yet using pnpm or bun catalogs can reach the catalog migration commands, but those should be evaluated against how the package manager handles catalog resolution before committing.

Frequently asked questions

What does syncpack do?

syncpack finds and fixes dependency version mismatches across multiple package.json files in a JavaScript monorepo. It can also update outdated versions from the npm registry, enforce version policies, ban specific dependencies, and auto-migrate entries to pnpm or bun catalogs.

How do I install syncpack?

Run npm install --save-dev syncpack. The package delivers a compiled Rust binary for each supported platform, distributed through npm.

What is the difference between syncpack lint and syncpack fix?

lint reports every version mismatch it finds; fix resolves only the issues it can determine a correct resolution for automatically. Some mismatches require a developer decision and will remain in the lint output after fix has run.

Does syncpack work with pnpm catalogs?

Yes. The update command targets pnpm catalog entries with --dependency-types pnpmCatalog, and named catalogs are addressable with --dependency-types 'pnpmCatalog:react18'. syncpack can also auto-migrate existing package.json version entries into the catalog format.

Is syncpack written in Rust or JavaScript?

syncpack is written in Rust and ships as a compiled binary. It is distributed via npm so npm install --save-dev syncpack fetches the pre-compiled binary for the current platform.

Official sources

  1. JamieMason/syncpack on GitHub
  2. License: MIT
  3. Project website
  4. README
  5. Releases
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/jamiemason-syncpack.svg)](https://hysenlabs.com/projects/jamiemason-syncpack)