Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/npm/cli.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGar <gar+gh@danger.computer>2021-09-09 18:21:56 +0300
committerGar <gar+gh@danger.computer>2021-09-09 22:34:53 +0300
commit6c12500ae14a6f8b78e3ab091ee6cc8e2ea9fd23 (patch)
treebc03898baff2fd541485d865cfe038e1dfeadb2f
parent1ad0938243110d983284e8763da41a57b561563d (diff)
feat(install): very strict global npm engines
This will do an engines check when installing npm globally and fail if the new npm is known not to work in the current node version. It will not work for older npm versions because they don't have an engines field (it wasn't added till npm@6.14.0). It will at least prevent npm@7 from being installed in node@8. PR-URL: https://github.com/npm/cli/pull/3731 Credit: @wraithgar Close: #3731 Reviewed-by: @nlf
-rw-r--r--lib/install.js22
-rw-r--r--package-lock.json1
-rw-r--r--package.json1
-rw-r--r--test/lib/install.js140
4 files changed, 162 insertions, 2 deletions
diff --git a/lib/install.js b/lib/install.js
index 661176397..1589ff589 100644
--- a/lib/install.js
+++ b/lib/install.js
@@ -8,6 +8,8 @@ const log = require('npmlog')
const { resolve, join } = require('path')
const Arborist = require('@npmcli/arborist')
const runScript = require('@npmcli/run-script')
+const pacote = require('pacote')
+const checks = require('npm-install-checks')
const ArboristWorkspaceCmd = require('./workspaces/arborist-cmd.js')
class Install extends ArboristWorkspaceCmd {
@@ -126,6 +128,23 @@ class Install extends ArboristWorkspaceCmd {
const ignoreScripts = this.npm.config.get('ignore-scripts')
const isGlobalInstall = this.npm.config.get('global')
const where = isGlobalInstall ? globalTop : this.npm.prefix
+ const forced = this.npm.config.get('force')
+ const isDev = this.npm.config.get('dev')
+ const scriptShell = this.npm.config.get('script-shell') || undefined
+
+ // be very strict about engines when trying to update npm itself
+ const npmInstall = args.find(arg => arg.startsWith('npm@') || arg === 'npm')
+ if (isGlobalInstall && npmInstall) {
+ const npmManifest = await pacote.manifest(npmInstall)
+ try {
+ checks.checkEngine(npmManifest, npmManifest.version, process.version)
+ } catch (e) {
+ if (forced)
+ this.npm.log.warn('install', `Forcing global npm install with incompatible version ${npmManifest.version} into node ${process.version}`)
+ else
+ throw e
+ }
+ }
// don't try to install the prefix into itself
args = args.filter(a => resolve(a) !== this.npm.prefix)
@@ -135,7 +154,7 @@ class Install extends ArboristWorkspaceCmd {
args = ['.']
// TODO: Add warnings for other deprecated flags? or remove this one?
- if (this.npm.config.get('dev'))
+ if (isDev)
log.warn('install', 'Usage of the `--dev` option is deprecated. Use `--include=dev` instead.')
const opts = {
@@ -150,7 +169,6 @@ class Install extends ArboristWorkspaceCmd {
await arb.reify(opts)
if (!args.length && !isGlobalInstall && !ignoreScripts) {
- const scriptShell = this.npm.config.get('script-shell') || undefined
const scripts = [
'preinstall',
'install',
diff --git a/package-lock.json b/package-lock.json
index 9abe13a85..8546bd714 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -127,6 +127,7 @@
"node-gyp": "^7.1.2",
"nopt": "^5.0.0",
"npm-audit-report": "^2.1.5",
+ "npm-install-checks": "^4.0.0",
"npm-package-arg": "^8.1.5",
"npm-pick-manifest": "^6.1.1",
"npm-profile": "^5.0.3",
diff --git a/package.json b/package.json
index e0363a49b..4531d1508 100644
--- a/package.json
+++ b/package.json
@@ -97,6 +97,7 @@
"node-gyp": "^7.1.2",
"nopt": "^5.0.0",
"npm-audit-report": "^2.1.5",
+ "npm-install-checks": "^4.0.0",
"npm-package-arg": "^8.1.5",
"npm-pick-manifest": "^6.1.1",
"npm-profile": "^5.0.3",
diff --git a/test/lib/install.js b/test/lib/install.js
index 6412b34c1..2cbee02e6 100644
--- a/test/lib/install.js
+++ b/test/lib/install.js
@@ -126,6 +126,146 @@ t.test('should install globally using Arborist', (t) => {
})
})
+t.test('npm i -g npm engines check success', (t) => {
+ const Install = t.mock('../../lib/install.js', {
+ '../../lib/utils/reify-finish.js': async () => {},
+ '@npmcli/arborist': function () {
+ this.reify = () => {}
+ },
+ pacote: {
+ manifest: () => {
+ return {
+ version: '100.100.100',
+ engines: {
+ node: '>1',
+ },
+ }
+ },
+ },
+ })
+ const npm = mockNpm({
+ globalDir: 'path/to/node_modules/',
+ config: {
+ global: true,
+ },
+ })
+ const install = new Install(npm)
+ install.exec(['npm'], er => {
+ if (er)
+ throw er
+ t.end()
+ })
+})
+
+t.test('npm i -g npm engines check failure', (t) => {
+ const Install = t.mock('../../lib/install.js', {
+ pacote: {
+ manifest: () => {
+ return {
+ _id: 'npm@1.2.3',
+ version: '100.100.100',
+ engines: {
+ node: '>1000',
+ },
+ }
+ },
+ },
+ })
+ const npm = mockNpm({
+ globalDir: 'path/to/node_modules/',
+ config: {
+ global: true,
+ },
+ })
+ const install = new Install(npm)
+ install.exec(['npm'], er => {
+ t.match(er, {
+ message: 'Unsupported engine',
+ pkgid: 'npm@1.2.3',
+ current: {
+ node: process.version,
+ npm: '100.100.100',
+ },
+ required: {
+ node: '>1000',
+ },
+ code: 'EBADENGINE',
+ })
+ t.end()
+ })
+})
+
+t.test('npm i -g npm engines check failure forced override', (t) => {
+ const Install = t.mock('../../lib/install.js', {
+ '../../lib/utils/reify-finish.js': async () => {},
+ '@npmcli/arborist': function () {
+ this.reify = () => {}
+ },
+ pacote: {
+ manifest: () => {
+ return {
+ _id: 'npm@1.2.3',
+ version: '100.100.100',
+ engines: {
+ node: '>1000',
+ },
+ }
+ },
+ },
+ })
+ const npm = mockNpm({
+ globalDir: 'path/to/node_modules/',
+ config: {
+ force: true,
+ global: true,
+ },
+ })
+ const install = new Install(npm)
+ install.exec(['npm'], er => {
+ if (er)
+ throw er
+ t.end()
+ })
+})
+
+t.test('npm i -g npm@version engines check failure', (t) => {
+ const Install = t.mock('../../lib/install.js', {
+ pacote: {
+ manifest: () => {
+ return {
+ _id: 'npm@1.2.3',
+ version: '100.100.100',
+ engines: {
+ node: '>1000',
+ },
+ }
+ },
+ },
+ })
+ const npm = mockNpm({
+ globalDir: 'path/to/node_modules/',
+ config: {
+ global: true,
+ },
+ })
+ const install = new Install(npm)
+ install.exec(['npm@100'], er => {
+ t.match(er, {
+ message: 'Unsupported engine',
+ pkgid: 'npm@1.2.3',
+ current: {
+ node: process.version,
+ npm: '100.100.100',
+ },
+ required: {
+ node: '>1000',
+ },
+ code: 'EBADENGINE',
+ })
+ t.end()
+ })
+})
+
t.test('completion to folder', async t => {
const Install = t.mock('../../lib/install.js', {
'../../lib/utils/reify-finish.js': async () => {},