Skip to content

Commit

Permalink
feat: add devEngines field
Browse files Browse the repository at this point in the history
This field is only looked at when in the top-level project, and takes precedence over `engines` when present

An alternative implementation instead of a new top-level field would be `publishConfig.engines`, which when present would override `engines` for NON top-level projects.
  • Loading branch information
ljharb committed Feb 27, 2024
1 parent 686a622 commit fb99c42
Show file tree
Hide file tree
Showing 10 changed files with 79 additions and 9 deletions.
3 changes: 2 additions & 1 deletion docs/lib/content/commands/npm-query.md
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,7 @@ npm query ":type(git)" | jq 'map(.name)' | xargs -I {} npm why {}
"peerDependencies": {},
"peerDependenciesMeta": {},
"engines": {},
"devEngines": {},
"os": [],
"cpu": [],
"workspaces": {},
Expand Down Expand Up @@ -158,7 +159,7 @@ $ npm query ':root>:outdated(in-range).prod' --no-expect-results

### Package lock only mode

If package-lock-only is enabled, only the information in the package lock (or shrinkwrap) is loaded. This means that information from the package.json files of your dependencies will not be included in the result set (e.g. description, homepage, engines).
If package-lock-only is enabled, only the information in the package lock (or shrinkwrap) is loaded. This means that information from the package.json files of your dependencies will not be included in the result set (e.g. description, homepage, engines, devEngines).

### Configuration

Expand Down
6 changes: 6 additions & 0 deletions docs/lib/content/configuring-npm/package-json.md
Original file line number Diff line number Diff line change
Expand Up @@ -1051,6 +1051,12 @@ Unless the user has set the
advisory only and will only produce warnings when your package is installed as a
dependency.

### devEngines

This field works exactly like the `engines` field, but it is only respected at the project root.

When present, the `engines` field is ignored.

### os

You can specify which operating systems your
Expand Down
13 changes: 9 additions & 4 deletions docs/lib/content/using-npm/developers.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,8 +61,9 @@ goes in that file. At the very least, you need:
* name: This should be a string that identifies your project. Please do
not use the name to specify that it runs on node, or is in JavaScript.
You can use the "engines" field to explicitly state the versions of node
(or whatever else) that your program requires, and it's pretty well
assumed that it's JavaScript.
(or whatever else) that your program requires (and "devEngines" when the
requirements for developers differ from the requirements for users), and
it's pretty well assumed that it's JavaScript.

It does not necessarily need to match your github repository name.

Expand All @@ -71,8 +72,12 @@ goes in that file. At the very least, you need:
* version: A semver-compatible version.

* engines: Specify the versions of node (or whatever else) that your
program runs on. The node API changes a lot, and there may be bugs or
new functionality that you depend on. Be explicit.
program runs on. The node API changes a lot, and there may be bugs or
new functionality that you depend on. Be explicit.

* devEngines: Specify the versions of node (or whatever else) that your
program needs for development. The node API changes a lot, and there may
be bugs or new functionality that you depend on. Be explicit.

* author: Take some credit.

Expand Down
6 changes: 3 additions & 3 deletions node_modules/npm-install-checks/lib/index.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
const semver = require('semver')

const checkEngine = (target, npmVer, nodeVer, force = false) => {
const checkEngine = ({ devEngines, engines, _id: pkgid }, npmVer, nodeVer, force = false, isProjectRoot = false) => {
const nodev = force ? null : nodeVer
const eng = target.engines
const opt = { includePrerelease: true }
const eng = isProjectRoot ? devEngines || engines : engines
if (!eng) {
return
}
Expand All @@ -12,7 +12,7 @@ const checkEngine = (target, npmVer, nodeVer, force = false) => {
const npmFail = npmVer && eng.npm && !semver.satisfies(npmVer, eng.npm, opt)
if (nodeFail || npmFail) {
throw Object.assign(new Error('Unsupported engine'), {
pkgid: target._id,
pkgid,
current: { node: nodeVer, npm: npmVer },
required: eng,
code: 'EBADENGINE',
Expand Down
1 change: 1 addition & 0 deletions test/lib/commands/publish.js
Original file line number Diff line number Diff line change
Expand Up @@ -753,6 +753,7 @@ t.test('manifest', async t => {
'tap',
'readme',
'engines',
'devEngines',
'workspaces',
]

Expand Down
2 changes: 1 addition & 1 deletion workspaces/arborist/lib/arborist/build-ideal-tree.js
Original file line number Diff line number Diff line change
Expand Up @@ -232,7 +232,7 @@ module.exports = cls => class IdealTreeBuilder extends cls {
for (const node of this.idealTree.inventory.values()) {
if (!node.optional) {
try {
checkEngine(node.package, npmVersion, nodeVersion, this[_force])
checkEngine(node.package, npmVersion, nodeVersion, this[_force], node.isProjectRoot)
} catch (err) {
if (engineStrict) {
throw err
Expand Down
1 change: 1 addition & 0 deletions workspaces/arborist/lib/shrinkwrap.js
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@ const pkgMetaKeys = [
'acceptDependencies',
'funding',
'engines',
'devEngines',
'os',
'cpu',
'_integrity',
Expand Down
24 changes: 24 additions & 0 deletions workspaces/arborist/test/arborist/build-ideal-tree.js
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,18 @@ t.test('fail on mismatched engine when engineStrict is set', async t => {
}), { code: 'EBADENGINE' })
})

t.test('fail on mismatched devEngine when engineStrict is set', async t => {
const path = resolve(fixtures, 'dev-engine-specification')

t.rejects(buildIdeal(path, {
...OPT,
nodeVersion: '12.18.4',
engineStrict: true,
}).then(() => {
throw new Error('failed to fail')
}), { code: 'EBADENGINE' })
})

t.test('fail on malformed package.json', t => {
const path = resolve(fixtures, 'malformed-json')

Expand Down Expand Up @@ -157,6 +169,18 @@ t.test('warn on mismatched engine when engineStrict is false', t => {
]))
})

t.test('warn on mismatched devEngine when engineStrict is false', t => {
const path = resolve(fixtures, 'dev-engine-specification')
const check = warningTracker()
return buildIdeal(path, {
...OPT,
nodeVersion: '12.18.4',
engineStrict: false,
}).then(() => t.match(check(), [
['warn', 'EBADENGINE'],
]))
})

t.test('fail on mismatched platform', async t => {
const path = resolve(fixtures, 'platform-specification')
t.rejects(buildIdeal(path, {
Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{
"name": "dev-engine-platform-test",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "",
"license": "ISC",
"dependencies": {
},
"devEngines": {
"node": "< 0.1"
}
}

0 comments on commit fb99c42

Please sign in to comment.