What It Does
The engine check validates Node.js version requirements by examining the engines.node field in package.json and comparing it against your currently running Node version.
Key Features:
Verifies engines.node field exists
Detects End-of-Life (EOL) Node versions
Fails if your current Node version is too old
Provides upgrade recommendations
Source: /src/checks/engine.ts
What It Checks
The check performs three validations:
Field Existence:
Warns if engines.node is missing from package.json
Suggests adding it with your current Node version
EOL Version Detection:
Warns if specified version is below Node 18 (EOL threshold)
Recommends upgrading to current LTS
Current Version Compatibility:
Fails if you’re running Node below the required version
Shows your version vs. required version
/src/checks/engine.ts:16-90
export async function checkEngine () : Promise < CheckResult > {
const pkgPath = path . join ( process . cwd (), "package.json" );
if ( ! fs . existsSync ( pkgPath )) {
return {
checkName: "engine" ,
status: "skip" ,
messages: [{ level: "info" , text: "No package.json found" }],
};
}
const pkg = JSON . parse ( fs . readFileSync ( pkgPath , "utf-8" ));
const messages : CheckResult [ "messages" ] = [];
let status : CheckResult [ "status" ] = "pass" ;
const currentMajor = getCurrentNodeMajor ();
// Check if engines.node is specified
if ( ! pkg . engines ?. node ) {
return {
checkName: "engine" ,
status: "warn" ,
messages: [
{
level: "warn" ,
text: 'No "engines.node" field in package.json' ,
},
{
level: "info" ,
text: `Add "engines": { "node": ">= ${ currentMajor } .0.0" } so consumers know what Node version is required` ,
},
],
};
}
const specifiedMin = parseMinNodeVersion ( pkg . engines . node );
if ( specifiedMin === null ) {
messages . push ({
level: "warn" ,
text: `Could not parse engines.node value: " ${ pkg . engines . node } "` ,
});
status = "warn" ;
} else {
// Check for EOL Node versions (anything below 18 as of 2024)
const EOL_THRESHOLD = 18 ;
if ( specifiedMin < EOL_THRESHOLD ) {
status = "warn" ;
messages . push ({
level: "warn" ,
text: `engines.node specifies >= ${ specifiedMin } , but Node ${ specifiedMin } is End-of-Life` ,
});
messages . push ({
level: "info" ,
text: `Consider bumping to >= ${ EOL_THRESHOLD } (current LTS)` ,
});
} else {
messages . push ({
level: "info" ,
text: `engines.node = " ${ pkg . engines . node } " — looks good` ,
});
}
// Check if currently running Node satisfies the requirement
if ( currentMajor < specifiedMin ) {
status = "fail" ;
messages . push ({
level: "error" ,
text: `You are running Node ${ currentMajor } but this project requires Node >= ${ specifiedMin } ` ,
});
}
}
return { checkName: "engine" , status , messages };
}
Example Output
Pass: Valid Engine
Warn: Missing engines.node
Warn: EOL Version
Fail: Incompatible Version
Skip: No package.json
✓ engine — engines.node = ">=18.0.0" — looks good
Why It Matters
Compatibility & Security Risks Not specifying or enforcing Node version requirements causes:
Runtime failures due to missing language features
Security vulnerabilities in EOL Node versions
Build failures in CI/CD pipelines
Dependency conflicts with packages requiring newer Node
Unpredictable behavior across team members
How to Fix
1. Add Missing engines.node
Check your current Node version:
node --version
# v20.11.0
Add to package.json:
{
"engines" : {
"node" : ">=20.0.0"
}
}
Best Practice: Use >= with the minimum major version your project supports.
2. Update EOL Version
If you’re specifying an End-of-Life Node version:
{
"engines": {
- "node": ">=14.0.0"
+ "node": ">=18.0.0"
}
}
Current Node.js LTS versions (2024):
Node 20: Active LTS (recommended)
Node 18: Maintenance LTS
Node 16 and below: End-of-Life ❌
3. Upgrade Your Node Version
If the check fails because your Node is too old:
Using nvm
Using n
Using Homebrew (macOS)
Direct Download
nvm install 20
nvm use 20
Verify the upgrade:
node --version
stackprobe audit --only engine
4. Enforce in CI
Add Node version checks to your CI workflow:
name : CI
on : [ push , pull_request ]
jobs :
test :
runs-on : ubuntu-latest
steps :
- uses : actions/checkout@v3
- uses : actions/setup-node@v3
with :
node-version-file : '.nvmrc"
- run: npm ci
- run: npm test
Create .nvmrc for consistency:
Version Parsing
The check handles various engines.node formats:
/src/checks/engine.ts:5-10
function parseMinNodeVersion ( enginesField : string ) : number | null {
// Handle patterns like: ">=16.0.0", "^18", "14.x", ">=14 <20"
const match = enginesField . match ( / ( \d + ) / );
if ( ! match ) return null ;
return parseInt ( match [ 1 ], 10 );
}
Supported formats:
>=18.0.0 → 18
^20 → 20
20.x → 20
>=18 <22 → 18 (takes first number)
lts/* → Cannot parse (shows warning)
EOL Threshold
The check currently flags Node versions below 18 as End-of-Life:
const EOL_THRESHOLD = 18 ;
This threshold is hardcoded and should be updated periodically as Node versions reach EOL.
Node.js Release Schedule:
Node 16: EOL September 2023 ❌
Node 18: Maintenance until April 2025 ⚠️
Node 20: Active LTS until April 2026 ✅
Node 22: Current (April 2024) ✅
See nodejs.org/en/about/releases for the full schedule.
Common Patterns
Supporting multiple versions
Use a range to support multiple major versions: {
"engines" : {
"node" : ">=18.0.0 <22.0.0"
}
}
Or use || for non-continuous ranges: {
"engines" : {
"node" : "^18.0.0 || ^20.0.0"
}
}
Use = to require a specific version (rare): {
"engines" : {
"node" : "20.11.0"
}
}
This is not recommended — it prevents patch updates and security fixes.
Also specify package manager versions: {
"engines" : {
"node" : ">=20.0.0" ,
"npm" : ">=10.0.0" ,
"pnpm" : ">=8.0.0"
}
}
Enable strict engine checking:
Testing multiple Node versions
Use CI matrix testing: strategy :
matrix :
node-version : [ 18 , 20 , 22 ]
steps :
- uses : actions/setup-node@v3
with :
node-version : ${{ matrix.node-version }}
Configuration
To disable this check:
Or run it exclusively:
stackprobe audit --only engine
Next Steps
Circular Check Detect circular dependency chains
Dependencies Check Check for outdated npm packages