Skip to content
Merged
107 changes: 26 additions & 81 deletions DEPENDENCY-OVERRIDES.md
Original file line number Diff line number Diff line change
@@ -1,89 +1,34 @@
# Dependency Overrides
# Dependency overrides

This document explains the npm dependency overrides in `package.json` and when they can be removed.
`package.json` keeps one scoped npm override. Direct dependencies stay pinned to exact versions;
`package-lock.json` pins the resolved transitive dependencies.

## Overview

The overrides section in `package.json` forces specific versions of transitive dependencies to resolve compatibility issues and eliminate deprecation warnings.
## Jest's deepmerge dependency

```json
"overrides": {
"babel-plugin-istanbul": "7.0.1",
"test-exclude": "7.0.1",
"glob": "10.5.0"
"jest-config": {
"deepmerge": "4.1.1"
}
}
```

## Why Each Override Exists

### 1. `glob: "10.5.0"`

**Issue:** `glob` versions prior to v9 are deprecated with the message "Glob versions prior to v9 are no longer supported".

**Problem:** Without overrides, Jest's transitive dependencies pull in `glob@7.x` through the following chain:

- `jest@30.2.0` → `@jest/transform@30.2.0` → `babel-plugin-istanbul@7.0.1` → `test-exclude@6.0.0` → `glob@^7.1.4`

**Solution:** Pin to `glob@10.5.0` which is a supported, modern version.

**Can be removed when:** Jest and its dependencies natively use `glob@9+` without requiring overrides. Check by removing this override and running `npm install` - if no deprecation warnings appear for glob, it's safe to remove.

### 2. `test-exclude: "7.0.1"`

**Issue:** `test-exclude@6.x` depends on the deprecated `glob@^7.1.4`.

**Problem:** `babel-plugin-istanbul@7.0.1` specifies `test-exclude@^6.0.0` in its dependencies, which pulls in the deprecated glob.

**Solution:** Override to `test-exclude@7.0.1` which uses `glob@^10.4.1` instead.

**Dependency comparison:**

- `test-exclude@6.0.0`: `glob@^7.1.4`, `minimatch@^3.0.4`
- `test-exclude@7.0.1`: `glob@^10.4.1`, `minimatch@^9.0.4`

**Can be removed when:** `babel-plugin-istanbul` updates its dependency on `test-exclude` to `^7.0.0`. Check the babel-plugin-istanbul package.json or changelog for this update.

### 3. `babel-plugin-istanbul: "7.0.1"`

**Issue:** Ensures consistent pinning across the dependency chain.

**Problem:** `@jest/transform@30.2.0` specifies `babel-plugin-istanbul@^7.0.1` which could resolve to different patch versions.

**Solution:** Pin to exact version for reproducible builds and to ensure our `test-exclude` override works correctly.

**Can be removed when:** This can potentially be removed now since `7.0.1` is currently the latest version. However, keeping it ensures the override chain remains stable. Remove only after confirming `test-exclude` override is no longer needed.

## Dependency Chain

```
jest@30.2.0
└── @jest/core@30.2.0
└── @jest/transform@30.2.0
└── babel-plugin-istanbul@^7.0.1 (overridden to 7.0.1)
└── test-exclude@^6.0.0 (overridden to 7.0.1)
└── glob@^10.4.1 (overridden to 10.5.0 for consistency)
```

## Verification

Run `scripts/check-overrides.ts` to verify if these overrides are still needed:

```bash
npx tsx scripts/check-overrides.ts
```

This script checks:

1. If `babel-plugin-istanbul` now depends on `test-exclude@^7.0.0`
2. If `test-exclude@7.x` is available and uses modern `glob`
3. If deprecation warnings still appear without overrides

## Periodic Review

These overrides should be reviewed when:

- Upgrading Jest to a new major/minor version
- `babel-plugin-istanbul` releases a new version
- Running `npm audit` or `npm outdated`

Run `npm run check-overrides` to verify if overrides are still needed.
`jest-config@30.5.2` requires `deepmerge@^4.3.1`. The latest deepmerge release is `4.3.1`.
[GHSA-gcqx-w48m-w2h4](https://github.com/advisories/GHSA-gcqx-w48m-w2h4)
reports prototype poisoning in deepmerge versions `>=4.2.0 <=4.3.1`.

No patched release is available. This override selects `4.1.1` to clear `npm audit`
by moving deepmerge outside the advisory's reported range. Version `4.1.1` predates
the prototype-pollution guard and is not safer; it does not fix the weakness.
The override applies only below `jest-config`, which uses the default merge export
to merge `preset.globals` with `options.globals`. We accept this risk only because
both values come from trusted repo config. The full Jest suite validates
compatibility with the downgrade, not its security.

Remove the override when deepmerge ships a patched compatible release or Jest drops
the dependency. Update Jest if needed, remove the override, regenerate the lockfile,
and require both `npm audit` and `npm test` to pass.

`npm run check-overrides` checks the latest upstream releases for deepmerge removal candidates only.
Exit codes are 0 for a candidate, 1 when the override is still needed, and 2 for a
lookup error. A candidate still requires advisory review, a clean audit, and tests.
53 changes: 53 additions & 0 deletions __tests__/check-overrides.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import { describe, expect, it } from '@jest/globals';
import { checkOverrides } from '../scripts/override-checker';

function npmResponses(overrides: Record<string, unknown> = {}) {
const responses: Record<string, unknown> = {
'deepmerge version': '4.3.1',
'jest-config version': '30.5.2',
'jest-config@30.5.2 dependencies': { deepmerge: '^4.3.1' },
...overrides,
};
return (pkg: string, field: string) => responses[`${pkg} ${field}`];
}

describe('checkOverrides', () => {
it('returns 0 when deepmerge is newer than 4.3.1', () => {
expect(checkOverrides(npmResponses({ 'deepmerge version': '4.3.2' }))).toBe(0);
});

it('returns 0 when the latest jest-config drops deepmerge', () => {
expect(
checkOverrides(
npmResponses({
'jest-config version': '31.0.0',
'jest-config@31.0.0 dependencies': {},
})
)
).toBe(0);
});

it('returns 1 when deepmerge is 4.3.1 and Jest still requires it', () => {
expect(checkOverrides(npmResponses())).toBe(1);
});

it.each(['deepmerge version', 'jest-config version', 'jest-config@30.5.2 dependencies'])(
'returns 2 when the %s lookup throws',
lookup => {
const responses = npmResponses();
expect(
checkOverrides((pkg, field) => {
if (`${pkg} ${field}` === lookup) throw new Error('Registry unavailable');
return responses(pkg, field);
})
).toBe(2);
}
);

it.each<[string, unknown]>([
['deepmerge version', 431],
['jest-config version', null],
])('returns 2 when %s is not a string', (lookup, version) => {
expect(checkOverrides(npmResponses({ [lookup]: version }))).toBe(2);
});
});
Loading
Loading