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

See
openjs-foundation/package-metadata-interoperability-collab-space#15 (comment)
for the schema
  • Loading branch information
ljharb committed Feb 27, 2024
1 parent 9214be9 commit fba38d8
Show file tree
Hide file tree
Showing 9 changed files with 114 additions and 14 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
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)

Check failure on line 198 in workspaces/arborist/lib/arborist/build-ideal-tree.js

View workflow job for this annotation

GitHub Actions / Lint

Multiple spaces found before 'this'
} 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"
}
}
}

0 comments on commit fba38d8

Please sign in to comment.