Preventing Unsafe Package Upgrades in package.json

Vadim KononovVadim Kononov
3 min read

Some packages should not be updated past a certain version—whether due to breaking changes, compatibility constraints, or known bugs in newer releases. This script helps prevent unsupported upgrades and gives developers clear reasons why certain versions are off limits.

To prevent that, we use a simple script in scripts/preinstall.js that enforces maximum allowed versions for specified packages. It's designed to fail fast, explain the problem clearly, and show developers how to fix it.

Prerequisites

Before using the script, install the required dependency:

yarn add --dev semver

Before proceeding, also wire up the script in your package.json like this:

"scripts": {
  "preinstall": "node ./scripts/preinstall.js"
}

The Script

💡
Package limitations are defined in versionConstraints.
const fs = require('fs');
let semver;

try {
  semver = require('semver');
} catch (err) {
  console.warn(`
⚠️  'semver' module not found. Skipping version checks.
    Run 'yarn install' again after dependencies are installed.
`);
  process.exit(0);
}

const pkg = JSON.parse(fs.readFileSync('./package.json', 'utf8'));

// Define your constraints here
const versionConstraints = {
  react: {maxVersion: '18.3.1', reason: 'react-rails is not yet compatible with React 19+'},
  'react-dom': {maxVersion: '18.3.1', reason: 'react-rails is not yet compatible with React 19+'}
};

const errors = [];
const fixes = [];

for (const [pkgName, constraint] of Object.entries(versionConstraints)) {
  const declaredVersion = pkg.dependencies?.[pkgName] || pkg.devDependencies?.[pkgName];

  if (declaredVersion) {
    let minDeclared;

    try {
      minDeclared = semver.minVersion(declaredVersion);
    }
    catch (e) {
      errors.push(`${pkgName}@${declaredVersion} may exceed allowed version ${constraint.maxVersion} (${constraint.reason})`);
      fixes.push(`${pkgName}@~${constraint.maxVersion}`);
      continue;
    }

    if (!minDeclared) {
      errors.push(`${pkgName}@${declaredVersion} may exceed allowed version ${constraint.maxVersion} (${constraint.reason})`);
      fixes.push(`${pkgName}@~${constraint.maxVersion}`);
    }
    else if (semver.gt(minDeclared, constraint.maxVersion)) {
      errors.push(`${pkgName}@${declaredVersion} exceeds allowed version ${constraint.maxVersion} (${constraint.reason})`);
      fixes.push(`${pkgName}@~${constraint.maxVersion}`);
    }
  }
}

if (errors.length > 0) {
  console.error(`
🚫 One or more packages exceed allowed versions. Please fix the following:
   - ${errors.join('\n   - ')}`);

  if (fixes.length > 0) {
    console.error(`
💡 To fix, run:
   > yarn add ${fixes.join(' ')} --ignore-scripts
`);
  }

  process.exit(1);
}

Example Output

If a developer changes package.json to include:

"react": "^19.1.0"

They'll get:

🚫 One or more packages exceed allowed versions. Please fix the following:
   - react@^19.1.0 exceeds allowed version 18.3.1 (react-rails is not yet compatible with React 19+)

💡 To fix, run:
   > yarn add react@~18.3.1 --ignore-scripts

If an invalid version like "latest" is used:

🚫 One or more packages exceed allowed versions. Please fix the following:
   - react@latest may exceed allowed version 18.3.1 (react-rails is not yet compatible with React 19+)

💡 To fix, run:
   > yarn add react@~18.3.1 --ignore-scripts

Features

  • ✅ Enforces a max version cap on any package

  • ✅ Covers both dependencies and devDependencies

  • ✅ Handles common semver ranges like ^, ~, etc.

  • ✅ Detects invalid version formats like latest, file:, link:

  • ✅ Suggests copy-pasteable fix commands scoped only to the offending packages

Limitations

  • yarn upgrade --latest still updates the lockfile and breaks constraints — this script catches it after the fact.

  • ❌ Does not check transitive dependencies (e.g., indirect React installations)

  • ❌ Ignores peer and optional dependencies

  • ❌ Checks declared versions only (not actual installs in node_modules)

0
Subscribe to my newsletter

Read articles from Vadim Kononov directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Vadim Kononov
Vadim Kononov

I am an accomplished Solution Architect, Full Stack Developer and DevOps Specialist with a passion for creative leadership and mentorship, business optimization and technical direction, and ingenious solutions to complex problems. I am especially interested in App & Web Development, Cyber Security, Cloud Computing, Data Science, Open Source Software, Statistical Analysis and Discrete Mathematics.