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