Skip to content

Commit fb99c42

Browse files
committed
feat: add devEngines field
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.
1 parent 686a622 commit fb99c42

File tree

10 files changed

+79
-9
lines changed

10 files changed

+79
-9
lines changed

docs/lib/content/commands/npm-query.md

+2-1
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,7 @@ npm query ":type(git)" | jq 'map(.name)' | xargs -I {} npm why {}
125125
"peerDependencies": {},
126126
"peerDependenciesMeta": {},
127127
"engines": {},
128+
"devEngines": {},
128129
"os": [],
129130
"cpu": [],
130131
"workspaces": {},
@@ -158,7 +159,7 @@ $ npm query ':root>:outdated(in-range).prod' --no-expect-results
158159

159160
### Package lock only mode
160161

161-
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).
162+
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).
162163

163164
### Configuration
164165

docs/lib/content/configuring-npm/package-json.md

+6
Original file line numberDiff line numberDiff line change
@@ -1051,6 +1051,12 @@ Unless the user has set the
10511051
advisory only and will only produce warnings when your package is installed as a
10521052
dependency.
10531053

1054+
### devEngines
1055+
1056+
This field works exactly like the `engines` field, but it is only respected at the project root.
1057+
1058+
When present, the `engines` field is ignored.
1059+
10541060
### os
10551061

10561062
You can specify which operating systems your

docs/lib/content/using-npm/developers.md

+9-4
Original file line numberDiff line numberDiff line change
@@ -61,8 +61,9 @@ goes in that file. At the very least, you need:
6161
* name: This should be a string that identifies your project. Please do
6262
not use the name to specify that it runs on node, or is in JavaScript.
6363
You can use the "engines" field to explicitly state the versions of node
64-
(or whatever else) that your program requires, and it's pretty well
65-
assumed that it's JavaScript.
64+
(or whatever else) that your program requires (and "devEngines" when the
65+
requirements for developers differ from the requirements for users), and
66+
it's pretty well assumed that it's JavaScript.
6667

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

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

7374
* engines: Specify the versions of node (or whatever else) that your
74-
program runs on. The node API changes a lot, and there may be bugs or
75-
new functionality that you depend on. Be explicit.
75+
program runs on. The node API changes a lot, and there may be bugs or
76+
new functionality that you depend on. Be explicit.
77+
78+
* devEngines: Specify the versions of node (or whatever else) that your
79+
program needs for development. The node API changes a lot, and there may
80+
be bugs or new functionality that you depend on. Be explicit.
7681

7782
* author: Take some credit.
7883

node_modules/npm-install-checks/lib/index.js

+3-3
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
const semver = require('semver')
22

3-
const checkEngine = (target, npmVer, nodeVer, force = false) => {
3+
const checkEngine = ({ devEngines, engines, _id: pkgid }, npmVer, nodeVer, force = false, isProjectRoot = false) => {
44
const nodev = force ? null : nodeVer
5-
const eng = target.engines
65
const opt = { includePrerelease: true }
6+
const eng = isProjectRoot ? devEngines || engines : engines
77
if (!eng) {
88
return
99
}
@@ -12,7 +12,7 @@ const checkEngine = (target, npmVer, nodeVer, force = false) => {
1212
const npmFail = npmVer && eng.npm && !semver.satisfies(npmVer, eng.npm, opt)
1313
if (nodeFail || npmFail) {
1414
throw Object.assign(new Error('Unsupported engine'), {
15-
pkgid: target._id,
15+
pkgid,
1616
current: { node: nodeVer, npm: npmVer },
1717
required: eng,
1818
code: 'EBADENGINE',

test/lib/commands/publish.js

+1
Original file line numberDiff line numberDiff line change
@@ -753,6 +753,7 @@ t.test('manifest', async t => {
753753
'tap',
754754
'readme',
755755
'engines',
756+
'devEngines',
756757
'workspaces',
757758
]
758759

workspaces/arborist/lib/arborist/build-ideal-tree.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -232,7 +232,7 @@ module.exports = cls => class IdealTreeBuilder extends cls {
232232
for (const node of this.idealTree.inventory.values()) {
233233
if (!node.optional) {
234234
try {
235-
checkEngine(node.package, npmVersion, nodeVersion, this[_force])
235+
checkEngine(node.package, npmVersion, nodeVersion, this[_force], node.isProjectRoot)
236236
} catch (err) {
237237
if (engineStrict) {
238238
throw err

workspaces/arborist/lib/shrinkwrap.js

+1
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,7 @@ const pkgMetaKeys = [
9292
'acceptDependencies',
9393
'funding',
9494
'engines',
95+
'devEngines',
9596
'os',
9697
'cpu',
9798
'_integrity',

workspaces/arborist/test/arborist/build-ideal-tree.js

+24
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,18 @@ t.test('fail on mismatched engine when engineStrict is set', async t => {
126126
}), { code: 'EBADENGINE' })
127127
})
128128

129+
t.test('fail on mismatched devEngine when engineStrict is set', async t => {
130+
const path = resolve(fixtures, 'dev-engine-specification')
131+
132+
t.rejects(buildIdeal(path, {
133+
...OPT,
134+
nodeVersion: '12.18.4',
135+
engineStrict: true,
136+
}).then(() => {
137+
throw new Error('failed to fail')
138+
}), { code: 'EBADENGINE' })
139+
})
140+
129141
t.test('fail on malformed package.json', t => {
130142
const path = resolve(fixtures, 'malformed-json')
131143

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

172+
t.test('warn on mismatched devEngine when engineStrict is false', t => {
173+
const path = resolve(fixtures, 'dev-engine-specification')
174+
const check = warningTracker()
175+
return buildIdeal(path, {
176+
...OPT,
177+
nodeVersion: '12.18.4',
178+
engineStrict: false,
179+
}).then(() => t.match(check(), [
180+
['warn', 'EBADENGINE'],
181+
]))
182+
})
183+
160184
t.test('fail on mismatched platform', async t => {
161185
const path = resolve(fixtures, 'platform-specification')
162186
t.rejects(buildIdeal(path, {

workspaces/arborist/test/fixtures/dev-engine-specification/package-lock.json

+16
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
{
2+
"name": "dev-engine-platform-test",
3+
"version": "1.0.0",
4+
"description": "",
5+
"main": "index.js",
6+
"scripts": {
7+
"test": "echo \"Error: no test specified\" && exit 1"
8+
},
9+
"author": "",
10+
"license": "ISC",
11+
"dependencies": {
12+
},
13+
"devEngines": {
14+
"node": "< 0.1"
15+
}
16+
}

0 commit comments

Comments
 (0)