Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add devEngines field #7253

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
24 changes: 24 additions & 0 deletions docs/lib/content/configuring-npm/package-json.md
Original file line number Diff line number Diff line change
Expand Up @@ -1066,6 +1066,30 @@ 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 similar to the `engines` field. It is only respected at the project root.

All properties of `devEngines` are optional. Here is a TypeScript interface describing the schema of the object:
```ts
interface DevEngines {
os?: DevEngineDependency | DevEngineDependency[];
cpu?: DevEngineDependency | DevEngineDependency[];
runtime?: DevEngineDependency | DevEngineDependency[];
packageManager?: DevEngineDependency | DevEngineDependency[];
}

interface DevEngineDependency {
name: string;
version?: string;
onFail?: 'ignore' | 'warn' | 'error';
}
```

`onFail` defaults to `error`. When an unknown `onFail` value is provided, it will error.

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
25 changes: 18 additions & 7 deletions node_modules/npm-install-checks/lib/index.js
Original file line number Diff line number Diff line change
@@ -1,18 +1,29 @@
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 }
if (!eng) {
return

let npmEngine = engines?.npm ?? '*';
let nodeEngine = engines?.node ?? '*';

if (isProjectRoot && devEngines?.packageManager) {
npmEngine ??= [].concat(devEngines?.packageManager ?? []).find(({ name }) => name === 'npm')?.version;
nodeEngine ??= [].concat(devEngines?.runtime ?? []).find(({ name }) => name === 'node')?.version;
}

const nodeFail = nodev && eng.node && !semver.satisfies(nodev, eng.node, opt)
const npmFail = npmVer && eng.npm && !semver.satisfies(npmVer, eng.npm, opt)
if (
(!npmEngine || npmEngine === '*')
|| (!nodeEngine || nodeEngine === '*')
) {
return;
}

const nodeFail = nodev && !semver.satisfies(nodev, nodeEngine, opt)
const npmFail = npmVer && !semver.satisfies(npmVer, npmEngine, 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
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 @@ -195,7 +195,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.options.force)
checkEngine(node.package, npmVersion, nodeVersion, this.options.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 @@ -93,6 +93,7 @@ const pkgMetaKeys = [
'acceptDependencies',
'funding',
'engines',
'devEngines',
'os',
'cpu',
'_integrity',
Expand Down
26 changes: 25 additions & 1 deletion workspaces/arborist/test/arborist/build-ideal-tree.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ t.teardown(stop)

const cache = t.testdir()

// track the warnings that are emitted. returns a function that removes
// track the warnings that are emitted. returns a function that removes
// the listener and provides the list of what it saw.
const warningTracker = () => {
const list = []
Expand Down 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,18 @@
{
"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": {
"runtime": {
"node": "< 0.1"
}
}
}
Loading