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(install): very strict global npm engines #3731

Merged
merged 1 commit into from Sep 9, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
22 changes: 20 additions & 2 deletions lib/install.js
Expand Up @@ -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 {
Expand Down Expand Up @@ -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)
Expand All @@ -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 = {
Expand All @@ -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',
Expand Down
1 change: 1 addition & 0 deletions package-lock.json

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

1 change: 1 addition & 0 deletions package.json
Expand Up @@ -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",
Expand Down
140 changes: 140 additions & 0 deletions test/lib/install.js
Expand Up @@ -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 () => {},
Expand Down