DEV Community

Cover image for I got tired of guessing semver bumps in PRs, so I built a tool
kyungseopk1m
kyungseopk1m

Posted on

I got tired of guessing semver bumps in PRs, so I built a tool

The problem

Every TypeScript library maintainer knows this moment.

You open a PR, and someone comments: "Wait, is this a major or minor bump?"

You look at the diff. You changed a return type somewhere. Or added a required parameter. Or renamed an interface property. Suddenly it's a 10-minute discussion about whether your change is breaking or not — and half the time, people just go with their gut.

Tools like conventional-commits help, but they depend on the developer correctly labeling their own commits. If I add a required parameter and write feat: instead of feat!:, nothing will catch that.


What if we just... looked at the types?

TypeScript already knows everything about your API surface. It knows what you exported, what the signatures look like, what changed between versions.

So I built semver-checks — a CLI that diffs your TypeScript API between two git refs and tells you exactly what semver bump is required.

npx semver-checks compare v1.0.0 HEAD
Enter fullscreen mode Exit fullscreen mode

Output:

BREAKING CHANGES (2):
  required-property-added: Required property 'timeout' was added
  return-type-changed: Return type of 'findUser' changed

FEATURES (1):
  export-added: Export 'createConfig' was added

Recommendation: MAJOR
Enter fullscreen mode Exit fullscreen mode

No opinions. No commit message parsing. Just TypeScript types, diffed mechanically.


How it works

semver-checks extracts your exported API at two points in time — using git refs or local paths — and compares them using 40+ classification rules.

MAJOR (breaking): export removed, required param added, return type changed, property type changed, enum member removed...

MINOR (new feature): export added, optional param added, interface method added, overload added...

PATCH: anything that doesn't affect existing consumers.

You can also use it programmatically:

import { compare } from 'semver-checks';

const report = await compare({
  oldSource: { type: 'git', ref: 'v1.0.0' },
  newSource: { type: 'path', path: '.' },
});

console.log(report.recommended); // 'major' | 'minor' | 'patch'
console.log(report.changes);     // ApiChange[]
Enter fullscreen mode Exit fullscreen mode

CI integration

The --strict flag exits with code 1 if breaking changes are detected — perfect for CI:

- name: Check for breaking changes
  run: npx semver-checks compare v1.0.0 HEAD --strict
Enter fullscreen mode Exit fullscreen mode

Now your pipeline fails if someone accidentally ships a breaking change without bumping the major version.


Honest limitations

It's not magic — here's what it can't do:

  • Behavioral changes (only API surface is analyzed)
  • Default exports (named exports only, for now)
  • Requires tsconfig.json in the analyzed project
  • Semantically equivalent types may produce false positives (string | number vs number | string)

Try it

npx semver-checks compare v1.0.0 HEAD
Enter fullscreen mode Exit fullscreen mode

GitHub: https://github.com/kyungseopk1m/semver-checks

If you hit edge cases or missing rules, open an issue — contributions welcome.


Tags: typescript opensource npm node

Top comments (0)