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
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
dependenciesanddevDependenciesβ 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 --lateststill 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)



