11cb0ef41Sopenharmony_ci/* eslint-disable camelcase */ 21cb0ef41Sopenharmony_ciconst fs = require('fs') 31cb0ef41Sopenharmony_ciconst util = require('util') 41cb0ef41Sopenharmony_ciconst readdir = util.promisify(fs.readdir) 51cb0ef41Sopenharmony_ciconst reifyFinish = require('../utils/reify-finish.js') 61cb0ef41Sopenharmony_ciconst log = require('../utils/log-shim.js') 71cb0ef41Sopenharmony_ciconst { resolve, join } = require('path') 81cb0ef41Sopenharmony_ciconst runScript = require('@npmcli/run-script') 91cb0ef41Sopenharmony_ciconst pacote = require('pacote') 101cb0ef41Sopenharmony_ciconst checks = require('npm-install-checks') 111cb0ef41Sopenharmony_ci 121cb0ef41Sopenharmony_ciconst ArboristWorkspaceCmd = require('../arborist-cmd.js') 131cb0ef41Sopenharmony_ciclass Install extends ArboristWorkspaceCmd { 141cb0ef41Sopenharmony_ci static description = 'Install a package' 151cb0ef41Sopenharmony_ci static name = 'install' 161cb0ef41Sopenharmony_ci 171cb0ef41Sopenharmony_ci // These are in the order they will show up in when running "-h" 181cb0ef41Sopenharmony_ci // If adding to this list, consider adding also to ci.js 191cb0ef41Sopenharmony_ci static params = [ 201cb0ef41Sopenharmony_ci 'save', 211cb0ef41Sopenharmony_ci 'save-exact', 221cb0ef41Sopenharmony_ci 'global', 231cb0ef41Sopenharmony_ci 'install-strategy', 241cb0ef41Sopenharmony_ci 'legacy-bundling', 251cb0ef41Sopenharmony_ci 'global-style', 261cb0ef41Sopenharmony_ci 'omit', 271cb0ef41Sopenharmony_ci 'include', 281cb0ef41Sopenharmony_ci 'strict-peer-deps', 291cb0ef41Sopenharmony_ci 'prefer-dedupe', 301cb0ef41Sopenharmony_ci 'package-lock', 311cb0ef41Sopenharmony_ci 'package-lock-only', 321cb0ef41Sopenharmony_ci 'foreground-scripts', 331cb0ef41Sopenharmony_ci 'ignore-scripts', 341cb0ef41Sopenharmony_ci 'audit', 351cb0ef41Sopenharmony_ci 'bin-links', 361cb0ef41Sopenharmony_ci 'fund', 371cb0ef41Sopenharmony_ci 'dry-run', 381cb0ef41Sopenharmony_ci 'cpu', 391cb0ef41Sopenharmony_ci 'os', 401cb0ef41Sopenharmony_ci 'libc', 411cb0ef41Sopenharmony_ci ...super.params, 421cb0ef41Sopenharmony_ci ] 431cb0ef41Sopenharmony_ci 441cb0ef41Sopenharmony_ci static usage = ['[<package-spec> ...]'] 451cb0ef41Sopenharmony_ci 461cb0ef41Sopenharmony_ci static async completion (opts) { 471cb0ef41Sopenharmony_ci const { partialWord } = opts 481cb0ef41Sopenharmony_ci // install can complete to a folder with a package.json, or any package. 491cb0ef41Sopenharmony_ci // if it has a slash, then it's gotta be a folder 501cb0ef41Sopenharmony_ci // if it starts with https?://, then just give up, because it's a url 511cb0ef41Sopenharmony_ci if (/^https?:\/\//.test(partialWord)) { 521cb0ef41Sopenharmony_ci // do not complete to URLs 531cb0ef41Sopenharmony_ci return [] 541cb0ef41Sopenharmony_ci } 551cb0ef41Sopenharmony_ci 561cb0ef41Sopenharmony_ci if (/\//.test(partialWord)) { 571cb0ef41Sopenharmony_ci // Complete fully to folder if there is exactly one match and it 581cb0ef41Sopenharmony_ci // is a folder containing a package.json file. If that is not the 591cb0ef41Sopenharmony_ci // case we return 0 matches, which will trigger the default bash 601cb0ef41Sopenharmony_ci // complete. 611cb0ef41Sopenharmony_ci const lastSlashIdx = partialWord.lastIndexOf('/') 621cb0ef41Sopenharmony_ci const partialName = partialWord.slice(lastSlashIdx + 1) 631cb0ef41Sopenharmony_ci const partialPath = partialWord.slice(0, lastSlashIdx) || '/' 641cb0ef41Sopenharmony_ci 651cb0ef41Sopenharmony_ci const isDirMatch = async sibling => { 661cb0ef41Sopenharmony_ci if (sibling.slice(0, partialName.length) !== partialName) { 671cb0ef41Sopenharmony_ci return false 681cb0ef41Sopenharmony_ci } 691cb0ef41Sopenharmony_ci 701cb0ef41Sopenharmony_ci try { 711cb0ef41Sopenharmony_ci const contents = await readdir(join(partialPath, sibling)) 721cb0ef41Sopenharmony_ci const result = (contents.indexOf('package.json') !== -1) 731cb0ef41Sopenharmony_ci return result 741cb0ef41Sopenharmony_ci } catch (er) { 751cb0ef41Sopenharmony_ci return false 761cb0ef41Sopenharmony_ci } 771cb0ef41Sopenharmony_ci } 781cb0ef41Sopenharmony_ci 791cb0ef41Sopenharmony_ci try { 801cb0ef41Sopenharmony_ci const siblings = await readdir(partialPath) 811cb0ef41Sopenharmony_ci const matches = [] 821cb0ef41Sopenharmony_ci for (const sibling of siblings) { 831cb0ef41Sopenharmony_ci if (await isDirMatch(sibling)) { 841cb0ef41Sopenharmony_ci matches.push(sibling) 851cb0ef41Sopenharmony_ci } 861cb0ef41Sopenharmony_ci } 871cb0ef41Sopenharmony_ci if (matches.length === 1) { 881cb0ef41Sopenharmony_ci return [join(partialPath, matches[0])] 891cb0ef41Sopenharmony_ci } 901cb0ef41Sopenharmony_ci // no matches 911cb0ef41Sopenharmony_ci return [] 921cb0ef41Sopenharmony_ci } catch (er) { 931cb0ef41Sopenharmony_ci return [] // invalid dir: no matching 941cb0ef41Sopenharmony_ci } 951cb0ef41Sopenharmony_ci } 961cb0ef41Sopenharmony_ci // Note: there used to be registry completion here, 971cb0ef41Sopenharmony_ci // but it stopped making sense somewhere around 981cb0ef41Sopenharmony_ci // 50,000 packages on the registry 991cb0ef41Sopenharmony_ci } 1001cb0ef41Sopenharmony_ci 1011cb0ef41Sopenharmony_ci async exec (args) { 1021cb0ef41Sopenharmony_ci // the /path/to/node_modules/.. 1031cb0ef41Sopenharmony_ci const globalTop = resolve(this.npm.globalDir, '..') 1041cb0ef41Sopenharmony_ci const ignoreScripts = this.npm.config.get('ignore-scripts') 1051cb0ef41Sopenharmony_ci const isGlobalInstall = this.npm.global 1061cb0ef41Sopenharmony_ci const where = isGlobalInstall ? globalTop : this.npm.prefix 1071cb0ef41Sopenharmony_ci const forced = this.npm.config.get('force') 1081cb0ef41Sopenharmony_ci const scriptShell = this.npm.config.get('script-shell') || undefined 1091cb0ef41Sopenharmony_ci 1101cb0ef41Sopenharmony_ci // be very strict about engines when trying to update npm itself 1111cb0ef41Sopenharmony_ci const npmInstall = args.find(arg => arg.startsWith('npm@') || arg === 'npm') 1121cb0ef41Sopenharmony_ci if (isGlobalInstall && npmInstall) { 1131cb0ef41Sopenharmony_ci const npmOptions = this.npm.flatOptions 1141cb0ef41Sopenharmony_ci const npmManifest = await pacote.manifest(npmInstall, npmOptions) 1151cb0ef41Sopenharmony_ci try { 1161cb0ef41Sopenharmony_ci checks.checkEngine(npmManifest, npmManifest.version, process.version) 1171cb0ef41Sopenharmony_ci } catch (e) { 1181cb0ef41Sopenharmony_ci if (forced) { 1191cb0ef41Sopenharmony_ci log.warn( 1201cb0ef41Sopenharmony_ci 'install', 1211cb0ef41Sopenharmony_ci /* eslint-disable-next-line max-len */ 1221cb0ef41Sopenharmony_ci `Forcing global npm install with incompatible version ${npmManifest.version} into node ${process.version}` 1231cb0ef41Sopenharmony_ci ) 1241cb0ef41Sopenharmony_ci } else { 1251cb0ef41Sopenharmony_ci throw e 1261cb0ef41Sopenharmony_ci } 1271cb0ef41Sopenharmony_ci } 1281cb0ef41Sopenharmony_ci } 1291cb0ef41Sopenharmony_ci 1301cb0ef41Sopenharmony_ci // don't try to install the prefix into itself 1311cb0ef41Sopenharmony_ci args = args.filter(a => resolve(a) !== this.npm.prefix) 1321cb0ef41Sopenharmony_ci 1331cb0ef41Sopenharmony_ci // `npm i -g` => "install this package globally" 1341cb0ef41Sopenharmony_ci if (where === globalTop && !args.length) { 1351cb0ef41Sopenharmony_ci args = ['.'] 1361cb0ef41Sopenharmony_ci } 1371cb0ef41Sopenharmony_ci 1381cb0ef41Sopenharmony_ci // throw usage error if trying to install empty package 1391cb0ef41Sopenharmony_ci // name to global space, e.g: `npm i -g ""` 1401cb0ef41Sopenharmony_ci if (where === globalTop && !args.every(Boolean)) { 1411cb0ef41Sopenharmony_ci throw this.usageError() 1421cb0ef41Sopenharmony_ci } 1431cb0ef41Sopenharmony_ci 1441cb0ef41Sopenharmony_ci const Arborist = require('@npmcli/arborist') 1451cb0ef41Sopenharmony_ci const opts = { 1461cb0ef41Sopenharmony_ci ...this.npm.flatOptions, 1471cb0ef41Sopenharmony_ci auditLevel: null, 1481cb0ef41Sopenharmony_ci path: where, 1491cb0ef41Sopenharmony_ci add: args, 1501cb0ef41Sopenharmony_ci workspaces: this.workspaceNames, 1511cb0ef41Sopenharmony_ci } 1521cb0ef41Sopenharmony_ci const arb = new Arborist(opts) 1531cb0ef41Sopenharmony_ci await arb.reify(opts) 1541cb0ef41Sopenharmony_ci 1551cb0ef41Sopenharmony_ci if (!args.length && !isGlobalInstall && !ignoreScripts) { 1561cb0ef41Sopenharmony_ci const scripts = [ 1571cb0ef41Sopenharmony_ci 'preinstall', 1581cb0ef41Sopenharmony_ci 'install', 1591cb0ef41Sopenharmony_ci 'postinstall', 1601cb0ef41Sopenharmony_ci 'prepublish', // XXX(npm9) should we remove this finally?? 1611cb0ef41Sopenharmony_ci 'preprepare', 1621cb0ef41Sopenharmony_ci 'prepare', 1631cb0ef41Sopenharmony_ci 'postprepare', 1641cb0ef41Sopenharmony_ci ] 1651cb0ef41Sopenharmony_ci for (const event of scripts) { 1661cb0ef41Sopenharmony_ci await runScript({ 1671cb0ef41Sopenharmony_ci path: where, 1681cb0ef41Sopenharmony_ci args: [], 1691cb0ef41Sopenharmony_ci scriptShell, 1701cb0ef41Sopenharmony_ci stdio: 'inherit', 1711cb0ef41Sopenharmony_ci banner: !this.npm.silent, 1721cb0ef41Sopenharmony_ci event, 1731cb0ef41Sopenharmony_ci }) 1741cb0ef41Sopenharmony_ci } 1751cb0ef41Sopenharmony_ci } 1761cb0ef41Sopenharmony_ci await reifyFinish(this.npm, arb) 1771cb0ef41Sopenharmony_ci } 1781cb0ef41Sopenharmony_ci} 1791cb0ef41Sopenharmony_cimodule.exports = Install 180