npm package: @jsleekr/semver-check
Description: Detect breaking API changes before they ship. Compares TypeScript/JavaScript API surfaces across versions and flags semver violations.
Author: JSLEEKR
Latest version: 1.0.0

<div align="center">

# 🔖 semver-check

### Detect breaking API changes before they ship

[![GitHub Stars](https://img.shields.io/github/stars/JSLEEKR/semver-check?style=for-the-badge&logo=github&color=yellow)](https://github.com/JSLEEKR/semver-check/stargazers)
[![License](https://img.shields.io/badge/license-MIT-blue?style=for-the-badge)](LICENSE)
[![TypeScript](https://img.shields.io/badge/TypeScript-5.4+-3178C6?style=for-the-badge&logo=typescript&logoColor=white)](https://www.typescriptlang.org/)
[![Tests](https://img.shields.io/badge/tests-334%20passing-brightgreen?style=for-the-badge)](#)
[![Node](https://img.shields.io/badge/Node.js-18+-339933?style=for-the-badge&logo=node.js&logoColor=white)](#)

<br/>

**Compare TypeScript/JavaScript API surfaces across versions and flag semver violations automatically**

[Why This Exists](#why-this-exists) | [Quick Start](#quick-start) | [What It Detects](#what-it-detects) | [CLI Reference](#cli-commands) | [Programmatic API](#programmatic-api) | [CI Integration](#ci-integration)

</div>

---

## Why This Exists

Every developer has shipped a patch release that silently broke a consumer's build. A parameter became required. A type narrowed. An export vanished. The version bump said `1.0.1` but the change was a breaking `2.0.0`. Users find out in their CI, not yours.

`semver-check` is [cargo-semver-checks](https://github.com/obi1kenobi/cargo-semver-checks) for the npm/TypeScript world -- it parses your public API surface, compares it against a previous version, classifies every change as breaking, feature, or fix, and tells you whether your version bump is correct before you publish.

- **20+ detection rules** -- removed exports, narrowed types, added required parameters, changed return types, and more
- **No compiler dependency** -- works via regex-based analysis, so it runs in any CI environment without a full TypeScript build
- **CI-ready exit codes** -- `--strict` mode exits non-zero on breaking changes, blocking the release pipeline automatically
- **Risk scoring** -- quantifies API stability across your entire public surface, not just the changed parts
- **LCS-based diff visualization** -- line-level and word-level diffs for type definition changes

---

## Install

```bash
npm install -g semver-check
```

Or run without installing:

```bash
npx semver-check compare v1.0.0 v2.0.0 --strict
```

## Requirements

- **Node.js >= 18.0.0**
- No compiler required -- pure regex-based analysis

---

## Quick Start

```bash
# Save a snapshot of your current API
semver-check snapshot

# Make changes to your code...

# Compare against the snapshot
semver-check compare 1.0.0

# See what version you should bump to
semver-check suggest 1.0.0

# Block CI on breaking changes
semver-check compare 1.0.0 --strict
```

---

## What It Detects

### Breaking Changes (require major bump)

| Rule | Description |
|------|-------------|
| `export-removed` | A previously exported symbol was removed |
| `export-renamed` | An exported symbol was renamed |
| `required-param-added` | A new required parameter was added to a function |
| `param-removed` | A parameter was removed from a function signature |
| `param-type-changed` | A parameter's type was changed |
| `param-became-required` | An optional parameter became required |
| `return-type-changed` | A function's return type changed |
| `async-changed` | A function's async status changed |
| `class-member-removed` | A public or protected class member was removed |
| `member-visibility-changed` | A member's visibility was narrowed (public → protected → private) |
| `member-type-changed` | A class property's type changed |
| `type-definition-changed` | A type alias or interface definition changed |
| `enum-member-removed` | An enum member was removed |
| `enum-value-changed` | An enum member's value changed |
| `default-export-changed` | The default export status of a module changed |

### Features (require minor bump)

| Rule | Description |
|------|-------------|
| `export-added` | A new symbol was exported |
| `optional-param-added` | A new optional parameter was added |
| `class-member-added` | A new public or protected class member was added |
| `enum-member-added` | A new enum member was added |
| `deprecation-added` | A symbol was marked as deprecated |

### Fixes (require patch bump)

| Rule | Description |
|------|-------------|
| `param-became-optional` | A required parameter became optional |
| `deprecation-removed` | A deprecation marker was removed |

---

## CLI Commands

### `compare [base] [head]`

Compare API surfaces between two versions or snapshots.

```bash
# Compare against a snapshot
semver-check compare 1.0.0

# Compare two git tags
semver-check compare v1.0.0 v2.0.0

# JSON output for CI pipelines
semver-check compare 1.0.0 --format json

# Fail (exit 1) on breaking changes
semver-check compare 1.0.0 --strict

# Markdown output for PR comments
semver-check compare 1.0.0 --format markdown
```

| Option | Description | Default |
|--------|-------------|---------|
| `--format <fmt>` | Output format: `text`, `json`, `markdown` | `text` |
| `--strict` | Exit non-zero on breaking changes | `false` |
| `--config <path>` | Path to config file | auto-detect |

---

### `snapshot`

Save the current API surface for later comparison. Snapshots are stored in `.semver-check/` and can be labeled for easy reference.

```bash
semver-check snapshot
semver-check snapshot --label pre-refactor
semver-check snapshot --label before-2.0
```

---

### `parse`

Display the current API surface -- all exported symbols, their types, parameters, and return values.

```bash
semver-check parse
semver-check parse --format json
```

Example output:

```
Exports (12):
  function createServer(port: number, opts?: ServerOptions): Server
  function destroyServer(server: Server): Promise<void>
  class Server
    + start(): Promise<void>
    + stop(): void
    + on(event: string, handler: Function): this
  interface ServerOptions
    port?: number
    timeout?: number
  type ServerStatus = 'running' | 'stopped' | 'error'
  enum LogLevel { DEBUG, INFO, WARN, ERROR }
```

---

### `suggest [base]`

Suggest the correct next version based on the detected changes.

```bash
semver-check suggest 1.0.0
# Suggested bump: minor -> 1.1.0

semver-check suggest 2.3.1
# Suggested bump: major -> 3.0.0 (breaking changes detected)
```

---

### `rules`

List all 20+ available detection rules with their categories and descriptions.

```bash
semver-check rules

# List only breaking rules
semver-check rules --category breaking

# List as JSON
semver-check rules --format json
```

---

### `init`

Scaffold a `.semver-check.json` configuration file in the current directory.

```bash
semver-check init
```

---

### `changelog [base]`

Generate a changelog entry from detected API changes.

```bash
semver-check changelog 1.0.0
# Outputs a Markdown-formatted changelog block
```

---

## Programmatic API

`semver-check` exposes a fluent builder API for integration into scripts and tools.

```typescript
import { semverCheck } from 'semver-check';

const result = semverCheck()
  .fromSource(oldCode, 'index.ts', '1.0.0')
  .toSource(newCode, 'index.ts', '2.0.0')
  .format('json')
  .strict()
  .check();

console.log(result.isBreaking);        // true/false
console.log(result.suggestedVersion);  // "2.0.0"
console.log(result.changes);           // array of ApiChange objects
console.log(result.formatted);         // formatted output string
```

### Working with individual modules

```typescript
import {
  parseApiSurface,
  compareApiSurfaces,
  classifyChanges,
  generateReport,
  suggestVersion,
  computeApiStats,
} from 'semver-check';

// Parse two versions
const before = parseApiSurface(oldSource, 'index.ts');
const after  = parseApiSurface(newSource, 'index.ts');

// Find what changed
const changes = compareApiSurfaces(before, after);

// Classify breaking / feature / fix
const classified = classifyChanges(changes);

// Suggest version bump
const suggestion = suggestVersion('1.4.2', classified);
console.log(suggestion.next); // "2.0.0"

// Risk scoring
const stats = computeApiStats(after);
console.log(stats.riskScore);          // 0.0 – 1.0
console.log(stats.breakingRuleCount);  // number of triggered breaking rules

// Format as markdown
const report = generateReport(classified, { format: 'markdown' });
```

### TypeScript types

```typescript
import type {
  ApiSurface,
  ApiChange,
  ChangeClassification,
  SemverSuggestion,
  ApiStats,
  SemverCheckConfig,
} from 'semver-check';
```

---

## Configuration

Create `.semver-check.json` in your project root, or add a `"semver-check"` field to `package.json`:

```json
{
  "entryPoints": ["src/index.ts"],
  "exclude": ["**/*.test.*", "**/__mocks__/**"],
  "includeInternal": false,
  "strict": false,
  "outputFormat": "text",
  "rules": {
    "deprecation-added": false,
    "param-became-optional": false
  }
}
```

| Field | Type | Default | Description |
|-------|------|---------|-------------|
| `entryPoints` | `string[]` | `["src/index.ts"]` | Files to treat as public API surface |
| `exclude` | `string[]` | `[]` | Glob patterns to exclude |
| `includeInternal` | `boolean` | `false` | Include symbols marked `@internal` |
| `strict` | `boolean` | `false` | Non-zero exit on any breaking change |
| `outputFormat` | `string` | `"text"` | Default output format |
| `rules` | `object` | `{}` | Set individual rules to `false` to disable |

---

## CI Integration

### GitHub Actions

Block a release when breaking changes are detected:

```yaml
name: Semver Check

on:
  pull_request:
    branches: [main]

jobs:
  semver:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 0

      - name: Check semver compliance
        run: |
          npx semver-check compare $(git describe --tags --abbrev=0) \
            --strict \
            --format json
```

Post results as a PR comment:

```yaml
      - name: Generate API diff
        id: diff
        run: |
          OUTPUT=$(npx semver-check compare $(git describe --tags --abbrev=0) --format markdown)
          echo "report<<EOF" >> $GITHUB_OUTPUT
          echo "$OUTPUT" >> $GITHUB_OUTPUT
          echo "EOF" >> $GITHUB_OUTPUT

      - name: Comment on PR
        uses: actions/github-script@v7
        with:
          script: |
            github.rest.issues.createComment({
              issue_number: context.issue.number,
              owner: context.repo.owner,
              repo: context.repo.repo,
              body: `## API Changes\n\n${{ steps.diff.outputs.report }}`
            })
```

### npm preversion hook

Enforce compliance before every `npm version` command:

```json
{
  "scripts": {
    "preversion": "semver-check compare $(git describe --tags --abbrev=0) --strict"
  }
}
```

### Exit Codes

| Code | Meaning |
|------|---------|
| `0` | No issues -- version bump is compliant |
| `1` | Breaking changes detected (in `--strict` mode) |
| `2` | Version bump is non-compliant with detected change level |
| `3` | Runtime error (file not found, parse failure, etc.) |

---

## How It Works

```
Source Files      Parse           Compare          Classify          Report
────────────      ──────────      ──────────       ──────────        ──────────
src/index.ts  →   Parser       →  Comparator    →  Classifier     →  Reporter
old snapshot      • exports       • added            • breaking        • text
git tag           • functions     • removed          • feature         • json
                  • classes       • modified         • fix             • markdown
                  • interfaces    • renamed
                  • types
                  • enums
```

1. **Parse** -- Extracts the public API surface from TypeScript/JavaScript files using regex-based analysis. No TypeScript compiler required -- works in any environment.
2. **Compare** -- Detects added, removed, and modified exports between two versions. Uses LCS-based diff for type definition changes.
3. **Classify** -- Maps each change to a semver bump level using 20+ built-in detection rules. Each rule targets a specific kind of breaking change.
4. **Report** -- Outputs results in text, JSON, or Markdown format. Includes migration hints, risk scores, and a suggested version bump.

---

## Architecture

```
src/
  types.ts          # Core type definitions (ApiSurface, ApiChange, etc.)
  parser.ts         # Public API surface extractor (regex-based, no tsc)
  comparator.ts     # Diff engine -- detects added/removed/modified exports
  classifier.ts     # Maps changes to breaking/feature/fix categories
  reporter.ts       # Output formatter (text/json/markdown)
  snapshot.ts       # API snapshot storage and retrieval
  rules.ts          # 20+ detection rules engine
  git-diff.ts       # Git integration (tag resolution, ref checkout)
  config.ts         # Configuration loader (.semver-check.json, package.json)
  differ.ts         # LCS-based line diff and word diff visualization
  type-analyzer.ts  # Deep union/object/array type comparison
  stats.ts          # API statistics and risk scoring
  ci.ts             # CI/CD integration (GitHub Actions outputs, changelog)
  api.ts            # Fluent programmatic builder API
  cli.ts            # CLI entry point
  index.ts          # Public re-exports
```

---

## Test Coverage

334 tests across 16 categories:

| Category | Tests | Coverage |
|----------|-------|----------|
| Parser (basic + advanced + JS) | 66 | Exports, functions, classes, interfaces, types, enums |
| Comparator (unit + edge cases) | 43 | Added/removed/modified, renames, edge cases |
| Classifier | 25 | All 20+ rules, bump level assignment |
| Reporter | 21 | text/json/markdown output, migration hints |
| Snapshot | 11 | Save, load, label, list |
| Config | 19 | JSON file, package.json field, defaults, merging |
| Differ | 17 | LCS, word diff, tokenizer |
| Type Analyzer | 25 | Union, object, array, generic type comparison |
| Rules | 14 | Rule engine, rule toggling, custom rules |
| Stats | 12 | Risk score, API size, complexity metrics |
| CI | 23 | GitHub Actions output, exit codes, PR summary |
| Git Diff | 13 | Tag resolution, ref checkout, real git repos |
| Integration | 10 | End-to-end compare flow |
| API (fluent builder) | 11 | Builder API, method chaining |
| Scenarios | 10 | Real-world Express, ORM, plugin, enum patterns |
| Compliance | 14 | Version suggestion accuracy, bump validation |

---

## FAQ

**Q: Does this require a TypeScript compiler?**
A: No. `semver-check` uses regex-based parsing and works on any `.ts` or `.js` file without `tsc` installed. This makes it fast and universally portable in CI environments.

**Q: How does it compare against a previous version?**
A: You have three options -- a saved snapshot (`semver-check snapshot`), a git tag (`compare v1.0.0`), or two explicit references (`compare v1.0.0 v2.0.0`). The git integration checks out the target ref to a temp directory and parses it in place.

**Q: Can I use it on JavaScript projects (no TypeScript)?**
A: Yes. The parser handles plain `.js` files. JSDoc annotations for types are recognized where present, though type analysis is limited compared to TypeScript.

**Q: What is the risk score?**
A: A number from 0.0 to 1.0 representing overall API stability. It factors in the number of breaking rules triggered, the size of the public surface, and the proportion of the surface that changed. Lower is more stable.

**Q: Can I disable specific rules?**
A: Yes. Set any rule to `false` in the `rules` section of your config. For example, `"deprecation-added": false` ignores deprecation additions when calculating the required bump level.

**Q: How is this different from `npm diff`?**
A: `npm diff` shows source code changes. `semver-check` parses the semantic meaning of those changes -- it tells you *whether* the changes require a major, minor, or patch bump, not just what lines changed.

---

## Troubleshooting

| Problem | Likely Cause | Solution |
|---------|--------------|----------|
| `No exports found` | Wrong entry point | Check `entryPoints` in config; default is `src/index.ts` |
| `Git tag not found` | Tag doesn't exist locally | Run `git fetch --tags` before comparing |
| `Parse error on line N` | Unusual TypeScript syntax | File an issue with a minimal repro; parser handles common patterns |
| `0 breaking changes` but build breaks | Dynamic API patterns | Add them to `entryPoints`; dynamic exports are not detected |
| Config file ignored | Wrong filename | Use `.semver-check.json` or `"semver-check"` key in `package.json` |

---

## License

MIT