11cb0ef41Sopenharmony_ciconst fs = require('fs')
21cb0ef41Sopenharmony_ciconst util = require('util')
31cb0ef41Sopenharmony_ciconst readdir = util.promisify(fs.readdir)
41cb0ef41Sopenharmony_ciconst { resolve } = require('path')
51cb0ef41Sopenharmony_ci
61cb0ef41Sopenharmony_ciconst npa = require('npm-package-arg')
71cb0ef41Sopenharmony_ciconst pkgJson = require('@npmcli/package-json')
81cb0ef41Sopenharmony_ciconst semver = require('semver')
91cb0ef41Sopenharmony_ci
101cb0ef41Sopenharmony_ciconst reifyFinish = require('../utils/reify-finish.js')
111cb0ef41Sopenharmony_ci
121cb0ef41Sopenharmony_ciconst ArboristWorkspaceCmd = require('../arborist-cmd.js')
131cb0ef41Sopenharmony_ciclass Link extends ArboristWorkspaceCmd {
141cb0ef41Sopenharmony_ci  static description = 'Symlink a package folder'
151cb0ef41Sopenharmony_ci  static name = 'link'
161cb0ef41Sopenharmony_ci  static usage = [
171cb0ef41Sopenharmony_ci    '[<package-spec>]',
181cb0ef41Sopenharmony_ci  ]
191cb0ef41Sopenharmony_ci
201cb0ef41Sopenharmony_ci  static params = [
211cb0ef41Sopenharmony_ci    'save',
221cb0ef41Sopenharmony_ci    'save-exact',
231cb0ef41Sopenharmony_ci    'global',
241cb0ef41Sopenharmony_ci    'install-strategy',
251cb0ef41Sopenharmony_ci    'legacy-bundling',
261cb0ef41Sopenharmony_ci    'global-style',
271cb0ef41Sopenharmony_ci    'strict-peer-deps',
281cb0ef41Sopenharmony_ci    'package-lock',
291cb0ef41Sopenharmony_ci    'omit',
301cb0ef41Sopenharmony_ci    'include',
311cb0ef41Sopenharmony_ci    'ignore-scripts',
321cb0ef41Sopenharmony_ci    'audit',
331cb0ef41Sopenharmony_ci    'bin-links',
341cb0ef41Sopenharmony_ci    'fund',
351cb0ef41Sopenharmony_ci    'dry-run',
361cb0ef41Sopenharmony_ci    ...super.params,
371cb0ef41Sopenharmony_ci  ]
381cb0ef41Sopenharmony_ci
391cb0ef41Sopenharmony_ci  static async completion (opts, npm) {
401cb0ef41Sopenharmony_ci    const dir = npm.globalDir
411cb0ef41Sopenharmony_ci    const files = await readdir(dir)
421cb0ef41Sopenharmony_ci    return files.filter(f => !/^[._-]/.test(f))
431cb0ef41Sopenharmony_ci  }
441cb0ef41Sopenharmony_ci
451cb0ef41Sopenharmony_ci  async exec (args) {
461cb0ef41Sopenharmony_ci    if (this.npm.global) {
471cb0ef41Sopenharmony_ci      throw Object.assign(
481cb0ef41Sopenharmony_ci        new Error(
491cb0ef41Sopenharmony_ci          'link should never be --global.\n' +
501cb0ef41Sopenharmony_ci          'Please re-run this command with --local'
511cb0ef41Sopenharmony_ci        ),
521cb0ef41Sopenharmony_ci        { code: 'ELINKGLOBAL' }
531cb0ef41Sopenharmony_ci      )
541cb0ef41Sopenharmony_ci    }
551cb0ef41Sopenharmony_ci    // install-links is implicitly false when running `npm link`
561cb0ef41Sopenharmony_ci    this.npm.config.set('install-links', false)
571cb0ef41Sopenharmony_ci
581cb0ef41Sopenharmony_ci    // link with no args: symlink the folder to the global location
591cb0ef41Sopenharmony_ci    // link with package arg: symlink the global to the local
601cb0ef41Sopenharmony_ci    args = args.filter(a => resolve(a) !== this.npm.prefix)
611cb0ef41Sopenharmony_ci    return args.length
621cb0ef41Sopenharmony_ci      ? this.linkInstall(args)
631cb0ef41Sopenharmony_ci      : this.linkPkg()
641cb0ef41Sopenharmony_ci  }
651cb0ef41Sopenharmony_ci
661cb0ef41Sopenharmony_ci  async linkInstall (args) {
671cb0ef41Sopenharmony_ci    // load current packages from the global space,
681cb0ef41Sopenharmony_ci    // and then add symlinks installs locally
691cb0ef41Sopenharmony_ci    const globalTop = resolve(this.npm.globalDir, '..')
701cb0ef41Sopenharmony_ci    const Arborist = require('@npmcli/arborist')
711cb0ef41Sopenharmony_ci    const globalOpts = {
721cb0ef41Sopenharmony_ci      ...this.npm.flatOptions,
731cb0ef41Sopenharmony_ci      Arborist,
741cb0ef41Sopenharmony_ci      path: globalTop,
751cb0ef41Sopenharmony_ci      global: true,
761cb0ef41Sopenharmony_ci      prune: false,
771cb0ef41Sopenharmony_ci    }
781cb0ef41Sopenharmony_ci    const globalArb = new Arborist(globalOpts)
791cb0ef41Sopenharmony_ci
801cb0ef41Sopenharmony_ci    // get only current top-level packages from the global space
811cb0ef41Sopenharmony_ci    const globals = await globalArb.loadActual({
821cb0ef41Sopenharmony_ci      filter: (node, kid) =>
831cb0ef41Sopenharmony_ci        !node.isRoot || args.some(a => npa(a).name === kid),
841cb0ef41Sopenharmony_ci    })
851cb0ef41Sopenharmony_ci
861cb0ef41Sopenharmony_ci    // any extra arg that is missing from the current
871cb0ef41Sopenharmony_ci    // global space should be reified there first
881cb0ef41Sopenharmony_ci    const missing = this.missingArgsFromTree(globals, args)
891cb0ef41Sopenharmony_ci    if (missing.length) {
901cb0ef41Sopenharmony_ci      await globalArb.reify({
911cb0ef41Sopenharmony_ci        ...globalOpts,
921cb0ef41Sopenharmony_ci        add: missing,
931cb0ef41Sopenharmony_ci      })
941cb0ef41Sopenharmony_ci    }
951cb0ef41Sopenharmony_ci
961cb0ef41Sopenharmony_ci    // get a list of module names that should be linked in the local prefix
971cb0ef41Sopenharmony_ci    const names = []
981cb0ef41Sopenharmony_ci    for (const a of args) {
991cb0ef41Sopenharmony_ci      const arg = npa(a)
1001cb0ef41Sopenharmony_ci      if (arg.type === 'directory') {
1011cb0ef41Sopenharmony_ci        const { content } = await pkgJson.normalize(arg.fetchSpec)
1021cb0ef41Sopenharmony_ci        names.push(content.name)
1031cb0ef41Sopenharmony_ci      } else {
1041cb0ef41Sopenharmony_ci        names.push(arg.name)
1051cb0ef41Sopenharmony_ci      }
1061cb0ef41Sopenharmony_ci    }
1071cb0ef41Sopenharmony_ci
1081cb0ef41Sopenharmony_ci    // npm link should not save=true by default unless you're
1091cb0ef41Sopenharmony_ci    // using any of --save-dev or other types
1101cb0ef41Sopenharmony_ci    const save =
1111cb0ef41Sopenharmony_ci      Boolean(
1121cb0ef41Sopenharmony_ci        (this.npm.config.find('save') !== 'default' &&
1131cb0ef41Sopenharmony_ci        this.npm.config.get('save')) ||
1141cb0ef41Sopenharmony_ci        this.npm.config.get('save-optional') ||
1151cb0ef41Sopenharmony_ci        this.npm.config.get('save-peer') ||
1161cb0ef41Sopenharmony_ci        this.npm.config.get('save-dev') ||
1171cb0ef41Sopenharmony_ci        this.npm.config.get('save-prod')
1181cb0ef41Sopenharmony_ci      )
1191cb0ef41Sopenharmony_ci    // create a new arborist instance for the local prefix and
1201cb0ef41Sopenharmony_ci    // reify all the pending names as symlinks there
1211cb0ef41Sopenharmony_ci    const localArb = new Arborist({
1221cb0ef41Sopenharmony_ci      ...this.npm.flatOptions,
1231cb0ef41Sopenharmony_ci      prune: false,
1241cb0ef41Sopenharmony_ci      path: this.npm.prefix,
1251cb0ef41Sopenharmony_ci      save,
1261cb0ef41Sopenharmony_ci    })
1271cb0ef41Sopenharmony_ci    await localArb.reify({
1281cb0ef41Sopenharmony_ci      ...this.npm.flatOptions,
1291cb0ef41Sopenharmony_ci      prune: false,
1301cb0ef41Sopenharmony_ci      path: this.npm.prefix,
1311cb0ef41Sopenharmony_ci      add: names.map(l => `file:${resolve(globalTop, 'node_modules', l).replace(/#/g, '%23')}`),
1321cb0ef41Sopenharmony_ci      save,
1331cb0ef41Sopenharmony_ci      workspaces: this.workspaceNames,
1341cb0ef41Sopenharmony_ci    })
1351cb0ef41Sopenharmony_ci
1361cb0ef41Sopenharmony_ci    await reifyFinish(this.npm, localArb)
1371cb0ef41Sopenharmony_ci  }
1381cb0ef41Sopenharmony_ci
1391cb0ef41Sopenharmony_ci  async linkPkg () {
1401cb0ef41Sopenharmony_ci    const wsp = this.workspacePaths
1411cb0ef41Sopenharmony_ci    const paths = wsp && wsp.length ? wsp : [this.npm.prefix]
1421cb0ef41Sopenharmony_ci    const add = paths.map(path => `file:${path.replace(/#/g, '%23')}`)
1431cb0ef41Sopenharmony_ci    const globalTop = resolve(this.npm.globalDir, '..')
1441cb0ef41Sopenharmony_ci    const Arborist = require('@npmcli/arborist')
1451cb0ef41Sopenharmony_ci    const arb = new Arborist({
1461cb0ef41Sopenharmony_ci      ...this.npm.flatOptions,
1471cb0ef41Sopenharmony_ci      Arborist,
1481cb0ef41Sopenharmony_ci      path: globalTop,
1491cb0ef41Sopenharmony_ci      global: true,
1501cb0ef41Sopenharmony_ci    })
1511cb0ef41Sopenharmony_ci    await arb.reify({
1521cb0ef41Sopenharmony_ci      add,
1531cb0ef41Sopenharmony_ci    })
1541cb0ef41Sopenharmony_ci    await reifyFinish(this.npm, arb)
1551cb0ef41Sopenharmony_ci  }
1561cb0ef41Sopenharmony_ci
1571cb0ef41Sopenharmony_ci  // Returns a list of items that can't be fulfilled by
1581cb0ef41Sopenharmony_ci  // things found in the current arborist inventory
1591cb0ef41Sopenharmony_ci  missingArgsFromTree (tree, args) {
1601cb0ef41Sopenharmony_ci    if (tree.isLink) {
1611cb0ef41Sopenharmony_ci      return this.missingArgsFromTree(tree.target, args)
1621cb0ef41Sopenharmony_ci    }
1631cb0ef41Sopenharmony_ci
1641cb0ef41Sopenharmony_ci    const foundNodes = []
1651cb0ef41Sopenharmony_ci    const missing = args.filter(a => {
1661cb0ef41Sopenharmony_ci      const arg = npa(a)
1671cb0ef41Sopenharmony_ci      const nodes = tree.children.values()
1681cb0ef41Sopenharmony_ci      const argFound = [...nodes].every(node => {
1691cb0ef41Sopenharmony_ci        // TODO: write tests for unmatching version specs, this is hard to test
1701cb0ef41Sopenharmony_ci        // atm but should be simple once we have a mocked registry again
1711cb0ef41Sopenharmony_ci        if (arg.name !== node.name /* istanbul ignore next */ || (
1721cb0ef41Sopenharmony_ci          arg.version &&
1731cb0ef41Sopenharmony_ci          /* istanbul ignore next */
1741cb0ef41Sopenharmony_ci          !semver.satisfies(node.version, arg.version)
1751cb0ef41Sopenharmony_ci        )) {
1761cb0ef41Sopenharmony_ci          foundNodes.push(node)
1771cb0ef41Sopenharmony_ci          return true
1781cb0ef41Sopenharmony_ci        }
1791cb0ef41Sopenharmony_ci      })
1801cb0ef41Sopenharmony_ci      return argFound
1811cb0ef41Sopenharmony_ci    })
1821cb0ef41Sopenharmony_ci
1831cb0ef41Sopenharmony_ci    // remote nodes from the loaded tree in order
1841cb0ef41Sopenharmony_ci    // to avoid dropping them later when reifying
1851cb0ef41Sopenharmony_ci    for (const node of foundNodes) {
1861cb0ef41Sopenharmony_ci      node.parent = null
1871cb0ef41Sopenharmony_ci    }
1881cb0ef41Sopenharmony_ci
1891cb0ef41Sopenharmony_ci    return missing
1901cb0ef41Sopenharmony_ci  }
1911cb0ef41Sopenharmony_ci}
1921cb0ef41Sopenharmony_cimodule.exports = Link
193