2018-04-20 18:26:37 -07:00
|
|
|
/* eslint-disable camelcase */
|
|
|
|
/* eslint-disable standard/no-callback-literal */
|
2020-10-02 17:52:19 -04:00
|
|
|
const fs = require('fs')
|
|
|
|
const util = require('util')
|
|
|
|
const readdir = util.promisify(fs.readdir)
|
2020-11-17 15:37:44 -05:00
|
|
|
const reifyFinish = require('./utils/reify-finish.js')
|
2020-10-02 17:52:19 -04:00
|
|
|
const log = require('npmlog')
|
|
|
|
const { resolve, join } = require('path')
|
|
|
|
const Arborist = require('@npmcli/arborist')
|
|
|
|
const runScript = require('@npmcli/run-script')
|
|
|
|
|
2021-03-11 17:54:23 -05:00
|
|
|
const BaseCommand = require('./base-command.js')
|
|
|
|
class Install extends BaseCommand {
|
|
|
|
/* istanbul ignore next - see test/lib/load-all-commands.js */
|
|
|
|
static get name () {
|
|
|
|
return 'install'
|
2021-03-04 17:40:28 -05:00
|
|
|
}
|
2020-10-02 17:52:19 -04:00
|
|
|
|
2021-03-04 17:40:28 -05:00
|
|
|
/* istanbul ignore next - see test/lib/load-all-commands.js */
|
2021-03-11 17:54:23 -05:00
|
|
|
static get usage () {
|
|
|
|
return [
|
|
|
|
'[<@scope>/]<pkg>',
|
|
|
|
'[<@scope>/]<pkg>@<tag>',
|
|
|
|
'[<@scope>/]<pkg>@<version>',
|
|
|
|
'[<@scope>/]<pkg>@<version range>',
|
|
|
|
'<alias>@npm:<name>',
|
|
|
|
'<folder>',
|
|
|
|
'<tarball file>',
|
|
|
|
'<tarball url>',
|
|
|
|
'<git:// url>',
|
|
|
|
'<github username>/<github project> [--save-prod|--save-dev|--save-optional|--save-peer] [--save-exact] [--no-save]',
|
|
|
|
]
|
2020-10-02 17:52:19 -04:00
|
|
|
}
|
2016-05-06 15:22:02 -07:00
|
|
|
|
2021-03-04 17:40:28 -05:00
|
|
|
async completion (opts) {
|
|
|
|
const { partialWord } = opts
|
|
|
|
// install can complete to a folder with a package.json, or any package.
|
|
|
|
// if it has a slash, then it's gotta be a folder
|
|
|
|
// if it starts with https?://, then just give up, because it's a url
|
|
|
|
if (/^https?:\/\//.test(partialWord)) {
|
|
|
|
// do not complete to URLs
|
|
|
|
return []
|
|
|
|
}
|
2011-11-21 09:48:45 -08:00
|
|
|
|
2021-03-04 17:40:28 -05:00
|
|
|
if (/\//.test(partialWord)) {
|
|
|
|
// Complete fully to folder if there is exactly one match and it
|
|
|
|
// is a folder containing a package.json file. If that is not the
|
|
|
|
// case we return 0 matches, which will trigger the default bash
|
|
|
|
// complete.
|
|
|
|
const lastSlashIdx = partialWord.lastIndexOf('/')
|
|
|
|
const partialName = partialWord.slice(lastSlashIdx + 1)
|
|
|
|
const partialPath = partialWord.slice(0, lastSlashIdx) || '/'
|
2011-11-21 09:48:45 -08:00
|
|
|
|
2021-03-04 17:40:28 -05:00
|
|
|
const annotatePackageDirMatch = async (sibling) => {
|
|
|
|
const fullPath = join(partialPath, sibling)
|
|
|
|
if (sibling.slice(0, partialName.length) !== partialName)
|
|
|
|
return null // not name match
|
2011-11-21 09:48:45 -08:00
|
|
|
|
2021-03-04 17:40:28 -05:00
|
|
|
try {
|
|
|
|
const contents = await readdir(fullPath)
|
|
|
|
return {
|
|
|
|
fullPath,
|
|
|
|
isPackage: contents.indexOf('package.json') !== -1,
|
|
|
|
}
|
|
|
|
} catch (er) {
|
|
|
|
return { isPackage: false }
|
|
|
|
}
|
|
|
|
}
|
2015-10-09 23:13:57 -07:00
|
|
|
|
2020-10-02 17:52:19 -04:00
|
|
|
try {
|
2021-03-04 17:40:28 -05:00
|
|
|
const siblings = await readdir(partialPath)
|
|
|
|
const matches = await Promise.all(
|
|
|
|
siblings.map(async sibling => {
|
|
|
|
return await annotatePackageDirMatch(sibling)
|
|
|
|
})
|
|
|
|
)
|
|
|
|
const match = matches.filter(el => !el || el.isPackage).pop()
|
|
|
|
if (match) {
|
|
|
|
// Success - only one match and it is a package dir
|
|
|
|
return [match.fullPath]
|
|
|
|
} else {
|
|
|
|
// no matches
|
|
|
|
return []
|
2015-10-09 23:13:57 -07:00
|
|
|
}
|
2020-11-01 07:54:36 +01:00
|
|
|
} catch (er) {
|
2021-03-04 17:40:28 -05:00
|
|
|
return [] // invalid dir: no matching
|
2012-05-05 22:33:06 -07:00
|
|
|
}
|
2017-12-07 14:05:23 -08:00
|
|
|
}
|
2021-03-04 17:40:28 -05:00
|
|
|
// Note: there used to be registry completion here,
|
|
|
|
// but it stopped making sense somewhere around
|
|
|
|
// 50,000 packages on the registry
|
|
|
|
}
|
|
|
|
|
|
|
|
exec (args, cb) {
|
|
|
|
this.install(args).then(() => cb()).catch(cb)
|
|
|
|
}
|
|
|
|
|
|
|
|
async install (args) {
|
|
|
|
// the /path/to/node_modules/..
|
|
|
|
const globalTop = resolve(this.npm.globalDir, '..')
|
|
|
|
const { ignoreScripts, global: isGlobalInstall } = this.npm.flatOptions
|
|
|
|
const where = isGlobalInstall ? globalTop : this.npm.prefix
|
2015-10-09 23:13:57 -07:00
|
|
|
|
2021-03-04 17:40:28 -05:00
|
|
|
// don't try to install the prefix into itself
|
|
|
|
args = args.filter(a => resolve(a) !== this.npm.prefix)
|
|
|
|
|
|
|
|
// `npm i -g` => "install this package globally"
|
|
|
|
if (where === globalTop && !args.length)
|
|
|
|
args = ['.']
|
|
|
|
|
|
|
|
// TODO: Add warnings for other deprecated flags? or remove this one?
|
|
|
|
if (this.npm.config.get('dev'))
|
|
|
|
log.warn('install', 'Usage of the `--dev` option is deprecated. Use `--include=dev` instead.')
|
|
|
|
|
|
|
|
const arb = new Arborist({
|
|
|
|
...this.npm.flatOptions,
|
|
|
|
path: where,
|
|
|
|
})
|
|
|
|
|
|
|
|
await arb.reify({
|
|
|
|
...this.npm.flatOptions,
|
|
|
|
add: args,
|
|
|
|
})
|
|
|
|
if (!args.length && !isGlobalInstall && !ignoreScripts) {
|
|
|
|
const { scriptShell } = this.npm.flatOptions
|
|
|
|
const scripts = [
|
|
|
|
'preinstall',
|
|
|
|
'install',
|
|
|
|
'postinstall',
|
|
|
|
'prepublish', // XXX should we remove this finally??
|
|
|
|
'preprepare',
|
|
|
|
'prepare',
|
|
|
|
'postprepare',
|
|
|
|
]
|
|
|
|
for (const event of scripts) {
|
|
|
|
await runScript({
|
|
|
|
path: where,
|
|
|
|
args: [],
|
|
|
|
scriptShell,
|
|
|
|
stdio: 'inherit',
|
|
|
|
stdioString: true,
|
|
|
|
banner: log.level !== 'silent',
|
|
|
|
event,
|
2016-01-28 18:11:35 -08:00
|
|
|
})
|
2015-10-09 23:13:57 -07:00
|
|
|
}
|
2017-05-09 14:46:02 -07:00
|
|
|
}
|
2021-03-04 17:40:28 -05:00
|
|
|
await reifyFinish(this.npm, arb)
|
2017-05-09 14:46:02 -07:00
|
|
|
}
|
2013-01-09 15:21:30 -08:00
|
|
|
}
|
2021-03-04 17:40:28 -05:00
|
|
|
module.exports = Install
|