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)
|
|
|
|
const npm = require('./npm.js')
|
|
|
|
const usageUtil = require('./utils/usage.js')
|
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')
|
|
|
|
|
2020-11-17 15:37:44 -05:00
|
|
|
const cmd = async (args, cb) => install(args).then(() => cb()).catch(cb)
|
|
|
|
|
|
|
|
const install = async args => {
|
2020-10-02 17:52:19 -04:00
|
|
|
// the /path/to/node_modules/..
|
|
|
|
const globalTop = resolve(npm.globalDir, '..')
|
|
|
|
const { ignoreScripts, global: isGlobalInstall } = npm.flatOptions
|
|
|
|
const where = isGlobalInstall ? globalTop : npm.prefix
|
|
|
|
|
|
|
|
// don't try to install the prefix into itself
|
|
|
|
args = args.filter(a => resolve(a) !== npm.prefix)
|
|
|
|
|
|
|
|
// `npm i -g` => "install this package globally"
|
2020-11-01 07:54:36 +01:00
|
|
|
if (where === globalTop && !args.length)
|
2020-10-02 17:52:19 -04:00
|
|
|
args = ['.']
|
2011-11-21 09:48:45 -08:00
|
|
|
|
2020-10-02 17:52:19 -04:00
|
|
|
// TODO: Add warnings for other deprecated flags? or remove this one?
|
2020-11-01 07:54:36 +01:00
|
|
|
if (npm.config.get('dev'))
|
2020-10-02 17:52:19 -04:00
|
|
|
log.warn('install', 'Usage of the `--dev` option is deprecated. Use `--include=dev` instead.')
|
2015-10-09 23:13:57 -07:00
|
|
|
|
2020-10-02 17:52:19 -04:00
|
|
|
const arb = new Arborist({
|
|
|
|
...npm.flatOptions,
|
2020-11-01 07:54:36 +01:00
|
|
|
path: where,
|
2020-10-02 17:52:19 -04:00
|
|
|
})
|
|
|
|
|
2020-11-17 15:37:44 -05:00
|
|
|
await arb.reify({
|
|
|
|
...npm.flatOptions,
|
|
|
|
add: args,
|
|
|
|
})
|
|
|
|
if (!args.length && !isGlobalInstall && !ignoreScripts) {
|
|
|
|
const { scriptShell } = 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,
|
2021-02-23 17:29:16 -05:00
|
|
|
banner: log.level !== 'silent',
|
2020-11-17 15:37:44 -05:00
|
|
|
event,
|
|
|
|
})
|
2020-10-02 17:52:19 -04:00
|
|
|
}
|
|
|
|
}
|
2020-11-17 15:37:44 -05:00
|
|
|
await reifyFinish(arb)
|
2020-10-02 17:52:19 -04:00
|
|
|
}
|
2016-05-06 15:22:02 -07:00
|
|
|
|
2020-10-02 17:52:19 -04:00
|
|
|
const usage = usageUtil(
|
2016-05-06 15:22:02 -07:00
|
|
|
'install',
|
2020-10-02 17:52:19 -04:00
|
|
|
'npm install (with no args, in package dir)' +
|
2016-05-06 15:22:02 -07:00
|
|
|
'\nnpm install [<@scope>/]<pkg>' +
|
|
|
|
'\nnpm install [<@scope>/]<pkg>@<tag>' +
|
|
|
|
'\nnpm install [<@scope>/]<pkg>@<version>' +
|
|
|
|
'\nnpm install [<@scope>/]<pkg>@<version range>' +
|
2019-11-18 21:01:39 +02:00
|
|
|
'\nnpm install <alias>@npm:<name>' +
|
2016-05-06 15:22:02 -07:00
|
|
|
'\nnpm install <folder>' +
|
|
|
|
'\nnpm install <tarball file>' +
|
|
|
|
'\nnpm install <tarball url>' +
|
|
|
|
'\nnpm install <git:// url>' +
|
|
|
|
'\nnpm install <github username>/<github project>',
|
2020-10-20 17:02:27 -04:00
|
|
|
'[--save-prod|--save-dev|--save-optional|--save-peer] [--save-exact] [--no-save]'
|
2016-05-06 15:22:02 -07:00
|
|
|
)
|
2011-11-21 09:48:45 -08:00
|
|
|
|
2021-03-01 11:38:43 -05:00
|
|
|
const completion = async (opts) => {
|
2020-10-02 17:52:19 -04:00
|
|
|
const { partialWord } = opts
|
2011-11-21 09:48:45 -08:00
|
|
|
// 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
|
2020-10-02 17:52:19 -04:00
|
|
|
if (/^https?:\/\//.test(partialWord)) {
|
2015-03-06 02:57:32 -06:00
|
|
|
// do not complete to URLs
|
2021-03-01 11:38:43 -05:00
|
|
|
return []
|
2015-03-06 02:57:32 -06:00
|
|
|
}
|
2011-11-21 09:48:45 -08:00
|
|
|
|
2020-10-02 17:52:19 -04:00
|
|
|
if (/\//.test(partialWord)) {
|
2015-03-06 02:57:32 -06:00
|
|
|
// 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.
|
2020-10-02 17:52:19 -04:00
|
|
|
const lastSlashIdx = partialWord.lastIndexOf('/')
|
|
|
|
const partialName = partialWord.slice(lastSlashIdx + 1)
|
|
|
|
const partialPath = partialWord.slice(0, lastSlashIdx) || '/'
|
2011-11-21 09:48:45 -08:00
|
|
|
|
2020-10-02 17:52:19 -04:00
|
|
|
const annotatePackageDirMatch = async (sibling) => {
|
|
|
|
const fullPath = join(partialPath, sibling)
|
2020-11-01 07:54:36 +01:00
|
|
|
if (sibling.slice(0, partialName.length) !== partialName)
|
2020-10-02 17:52:19 -04:00
|
|
|
return null // not name match
|
2015-10-09 23:13:57 -07:00
|
|
|
|
2020-10-02 17:52:19 -04:00
|
|
|
try {
|
|
|
|
const contents = await readdir(fullPath)
|
|
|
|
return {
|
|
|
|
fullPath,
|
2020-11-01 07:54:36 +01:00
|
|
|
isPackage: contents.indexOf('package.json') !== -1,
|
2015-10-09 23:13:57 -07:00
|
|
|
}
|
2020-11-01 07:54:36 +01:00
|
|
|
} catch (er) {
|
2020-10-02 17:52:19 -04:00
|
|
|
return { isPackage: false }
|
2012-05-05 22:33:06 -07:00
|
|
|
}
|
2017-12-07 14:05:23 -08:00
|
|
|
}
|
2015-10-09 23:13:57 -07:00
|
|
|
|
2020-10-02 17:52:19 -04:00
|
|
|
try {
|
|
|
|
const siblings = await readdir(partialPath)
|
|
|
|
const matches = await Promise.all(
|
|
|
|
siblings.map(async sibling => {
|
|
|
|
return await annotatePackageDirMatch(sibling)
|
2016-01-28 18:11:35 -08:00
|
|
|
})
|
2020-10-02 17:52:19 -04:00
|
|
|
)
|
|
|
|
const match = matches.filter(el => !el || el.isPackage).pop()
|
|
|
|
if (match) {
|
|
|
|
// Success - only one match and it is a package dir
|
2021-03-01 11:38:43 -05:00
|
|
|
return [match.fullPath]
|
2020-10-02 17:52:19 -04:00
|
|
|
} else {
|
|
|
|
// no matches
|
2021-03-01 11:38:43 -05:00
|
|
|
return []
|
2015-10-09 23:13:57 -07:00
|
|
|
}
|
2020-11-01 07:54:36 +01:00
|
|
|
} catch (er) {
|
2021-03-01 11:38:43 -05:00
|
|
|
return [] // invalid dir: no matching
|
2017-05-09 14:46:02 -07:00
|
|
|
}
|
|
|
|
}
|
2020-10-02 17:52:19 -04:00
|
|
|
// Note: there used to be registry completion here,
|
|
|
|
// but it stopped making sense somewhere around
|
|
|
|
// 50,000 packages on the registry
|
2013-01-09 15:21:30 -08:00
|
|
|
}
|
|
|
|
|
2020-11-17 15:37:44 -05:00
|
|
|
module.exports = Object.assign(cmd, { usage, completion })
|