DEV Community

skif: replacing sherif, syncpack and most of knip with one Rust binary

I was working on restructuring a very large monorepo with disparate package managers and versions into a single PNPM workspace. When I was finished I decided I am going to enforce certain conventions so it could not drift again without failing CI. I set this up, and found myself using several different tools - Sherif, Syncpack and Knip - to ensure that the repository doesn't drift again. This setup, while working, is rather clanky and also suffered from some issues - mainly performance, but also a few bugs.

I therefore decided I will build my own tool - forking Sherif as a baseline, and building on top of it while substantially rewriting and extending its capabilities. The result - still early - is skif: a Rust linter and enforcement tool for dependencies and monorepos.

skif replaces sherif and syncpack, and most of what knip does (the aim is to offer a full replacement for Knip in the future) in a single, ultra fast, binary.

npx skif-cli              # lint the workspace or package you are in
npx skif-cli --fix        # apply safe fixes
npx skif-cli migrate      # turn sherif, syncpack and knip config into a skif.toml
Enter fullscreen mode Exit fullscreen mode

Where it came from

skif started as a fork of sherif, and the workspace and manifest rules are ported from it. The version
and catalog policies follow syncpack's model. The dead-code analysis follows knip's issue types.

It has since gone past the fork. It ports every sherif rule, adds new ones and fixes, and runs faster on every
target measured. On the syncpack side it enforces version, range, banned-dependency and catalog policy, and on a seeded workspace it reports the same 4,568 mismatched
instances syncpack does.

What it deliberately does not do is sync versions. skif is a linter: it tells you a policy is broken and
fails CI. Moving versions in step is left to specialized tools or the package manager's themselves.

What it checks

42 rules:

  • Manifests and workspace: empty dependency maps, types in dependencies, root private and packageManager, unsorted dependencies, workspace globs that match nothing, members without a package.json.
  • Version policy: mismatched versions, semver range policy, banned dependencies, catalogs (missing entries, unused entries, references that do not resolve), workspace: protocol, peer dependency compatibility.
  • Dead code: unused files, exports, types, enum members and dependencies; unlisted dependencies and binaries; unresolved imports; circular imports. Leveraging the OXC ecosystem for this.

It also adds some new rules, such as incompatible-peer-dependencies and workspace-protocol.

Skif lifts the idea of framework plugins - which allow it to understand different types of entrypoints (e.g. Next.JS, Vite, Storybook etc.). But unlike Knip, which loads these files, Skif doesn't actually load code - it only statically analyses it. This removes a potential surface for code execution, and is altogether much faster, although it does bring in the potential to some imprecision.

Supported package managers

Skif supports all major package managers - npm, pnpm, Yarn classic, Yarn Berry as well as Bun. It does not support Deno, although if there is a need it might in the future. It also supports Rush and Lerna workspaces.

Speed

Medians of 15 runs on a 4-vCPU Linux VM, pinned public repositories, dependencies installed:

Repository skif knip
create-t3-turbo 32 ms 2.0 s 61x
vue-core 62 ms 1.7 s 27x
tanstack-query 375 ms 4.5 s 12x
npm-cli 99 ms 968 ms 10x
babel 884 ms 7.2 s 8x

Manifest checks alone (--no-js) against sherif and syncpack:

Target skif sherif syncpack
create-t3-turbo 4.6 ms 36 ms 41 ms
babel 9.4 ms 37 ms 280 ms
2,000 generated packages 102 ms 130 ms 655 ms

sherif is already fast Rust, so the margin there is small: 1.3x at 2,000 packages. The real win is
knip, and not paying for three processes.

Reproduce with mise run bench:publish. The corpus is pinned in tools/bench/corpus.toml.

Accuracy

  • On a generated workspace with seeded manifest defects, skif finds exactly what sherif and syncpack find, defect for defect.
  • On a workspace with 33 seeded dead-code findings, placed next to live code reached through patterns that trip up static analysis (typeof import(), import.meta.glob, tsconfig paths, # subpath imports), skif scores 100% precision and 100% recall. knip 6.44.1 scores 77% precision and 100% recall.
  • On nine real repositories the agreement with knip is published per issue type. Unused exports match closely: 4,691 shared findings, 98% of skif's and 94% of knip's. Unused dependencies match less closely. Where it still differs from knip is listed rather than hidden.

Try it

npx skif-cli migrate && npx skif-cli
Enter fullscreen mode Exit fullscreen mode

Also brew install goldziher/tap/skif, scoop install skif, cargo install skif-cli. npm needs macOS or
Linux; on Windows use scoop or cargo.

It is 0.2.0 and younger than all three tools it builds on. If it reports something wrong on your
repo, open an issue with the repo and I will fix it.

Repository URL: https://github.com/Goldziher/skif

Docs URL: https://goldziher.github.io/skif/

Top comments (0)