11cb0ef41Sopenharmony_ciconst archy = require('archy')
21cb0ef41Sopenharmony_ciconst pacote = require('pacote')
31cb0ef41Sopenharmony_ciconst semver = require('semver')
41cb0ef41Sopenharmony_ciconst npa = require('npm-package-arg')
51cb0ef41Sopenharmony_ciconst { depth } = require('treeverse')
61cb0ef41Sopenharmony_ciconst { readTree: getFundingInfo, normalizeFunding, isValidFunding } = require('libnpmfund')
71cb0ef41Sopenharmony_ci
81cb0ef41Sopenharmony_ciconst openUrl = require('../utils/open-url.js')
91cb0ef41Sopenharmony_ciconst ArboristWorkspaceCmd = require('../arborist-cmd.js')
101cb0ef41Sopenharmony_ci
111cb0ef41Sopenharmony_ciconst getPrintableName = ({ name, version }) => {
121cb0ef41Sopenharmony_ci  const printableVersion = version ? `@${version}` : ''
131cb0ef41Sopenharmony_ci  return `${name}${printableVersion}`
141cb0ef41Sopenharmony_ci}
151cb0ef41Sopenharmony_ci
161cb0ef41Sopenharmony_ciconst errCode = (msg, code) => Object.assign(new Error(msg), { code })
171cb0ef41Sopenharmony_ci
181cb0ef41Sopenharmony_ciclass Fund extends ArboristWorkspaceCmd {
191cb0ef41Sopenharmony_ci  static description = 'Retrieve funding information'
201cb0ef41Sopenharmony_ci  static name = 'fund'
211cb0ef41Sopenharmony_ci  static params = ['json', 'browser', 'unicode', 'workspace', 'which']
221cb0ef41Sopenharmony_ci  static usage = ['[<package-spec>]']
231cb0ef41Sopenharmony_ci
241cb0ef41Sopenharmony_ci  // XXX: maybe worth making this generic for all commands?
251cb0ef41Sopenharmony_ci  usageMessage (paramsObj = {}) {
261cb0ef41Sopenharmony_ci    let msg = `\`npm ${this.constructor.name}`
271cb0ef41Sopenharmony_ci    const params = Object.entries(paramsObj)
281cb0ef41Sopenharmony_ci    if (params.length) {
291cb0ef41Sopenharmony_ci      msg += ` ${this.constructor.usage}`
301cb0ef41Sopenharmony_ci    }
311cb0ef41Sopenharmony_ci    for (const [key, value] of params) {
321cb0ef41Sopenharmony_ci      msg += ` --${key}=${value}`
331cb0ef41Sopenharmony_ci    }
341cb0ef41Sopenharmony_ci    return `${msg}\``
351cb0ef41Sopenharmony_ci  }
361cb0ef41Sopenharmony_ci
371cb0ef41Sopenharmony_ci  // TODO
381cb0ef41Sopenharmony_ci  /* istanbul ignore next */
391cb0ef41Sopenharmony_ci  static async completion (opts, npm) {
401cb0ef41Sopenharmony_ci    const completion = require('../utils/completion/installed-deep.js')
411cb0ef41Sopenharmony_ci    return completion(npm, opts)
421cb0ef41Sopenharmony_ci  }
431cb0ef41Sopenharmony_ci
441cb0ef41Sopenharmony_ci  async exec (args) {
451cb0ef41Sopenharmony_ci    const spec = args[0]
461cb0ef41Sopenharmony_ci
471cb0ef41Sopenharmony_ci    let fundingSourceNumber = this.npm.config.get('which')
481cb0ef41Sopenharmony_ci    if (fundingSourceNumber != null) {
491cb0ef41Sopenharmony_ci      fundingSourceNumber = parseInt(fundingSourceNumber, 10)
501cb0ef41Sopenharmony_ci      if (isNaN(fundingSourceNumber) || fundingSourceNumber < 1) {
511cb0ef41Sopenharmony_ci        throw errCode(
521cb0ef41Sopenharmony_ci          `${this.usageMessage({ which: 'fundingSourceNumber' })} must be given a positive integer`,
531cb0ef41Sopenharmony_ci          'EFUNDNUMBER'
541cb0ef41Sopenharmony_ci        )
551cb0ef41Sopenharmony_ci      }
561cb0ef41Sopenharmony_ci    }
571cb0ef41Sopenharmony_ci
581cb0ef41Sopenharmony_ci    if (this.npm.global) {
591cb0ef41Sopenharmony_ci      throw errCode(
601cb0ef41Sopenharmony_ci        `${this.usageMessage()} does not support global packages`,
611cb0ef41Sopenharmony_ci        'EFUNDGLOBAL'
621cb0ef41Sopenharmony_ci      )
631cb0ef41Sopenharmony_ci    }
641cb0ef41Sopenharmony_ci
651cb0ef41Sopenharmony_ci    const where = this.npm.prefix
661cb0ef41Sopenharmony_ci    const Arborist = require('@npmcli/arborist')
671cb0ef41Sopenharmony_ci    const arb = new Arborist({ ...this.npm.flatOptions, path: where })
681cb0ef41Sopenharmony_ci    const tree = await arb.loadActual()
691cb0ef41Sopenharmony_ci
701cb0ef41Sopenharmony_ci    if (spec) {
711cb0ef41Sopenharmony_ci      await this.openFundingUrl({
721cb0ef41Sopenharmony_ci        path: where,
731cb0ef41Sopenharmony_ci        tree,
741cb0ef41Sopenharmony_ci        spec,
751cb0ef41Sopenharmony_ci        fundingSourceNumber,
761cb0ef41Sopenharmony_ci      })
771cb0ef41Sopenharmony_ci      return
781cb0ef41Sopenharmony_ci    }
791cb0ef41Sopenharmony_ci
801cb0ef41Sopenharmony_ci    // TODO: add !workspacesEnabled option handling to libnpmfund
811cb0ef41Sopenharmony_ci    const fundingInfo = getFundingInfo(tree, {
821cb0ef41Sopenharmony_ci      ...this.flatOptions,
831cb0ef41Sopenharmony_ci      Arborist,
841cb0ef41Sopenharmony_ci      workspaces: this.workspaceNames,
851cb0ef41Sopenharmony_ci    })
861cb0ef41Sopenharmony_ci
871cb0ef41Sopenharmony_ci    if (this.npm.config.get('json')) {
881cb0ef41Sopenharmony_ci      this.npm.output(this.printJSON(fundingInfo))
891cb0ef41Sopenharmony_ci    } else {
901cb0ef41Sopenharmony_ci      this.npm.output(this.printHuman(fundingInfo))
911cb0ef41Sopenharmony_ci    }
921cb0ef41Sopenharmony_ci  }
931cb0ef41Sopenharmony_ci
941cb0ef41Sopenharmony_ci  printJSON (fundingInfo) {
951cb0ef41Sopenharmony_ci    return JSON.stringify(fundingInfo, null, 2)
961cb0ef41Sopenharmony_ci  }
971cb0ef41Sopenharmony_ci
981cb0ef41Sopenharmony_ci  printHuman (fundingInfo) {
991cb0ef41Sopenharmony_ci    const unicode = this.npm.config.get('unicode')
1001cb0ef41Sopenharmony_ci    const seenUrls = new Map()
1011cb0ef41Sopenharmony_ci
1021cb0ef41Sopenharmony_ci    const tree = obj => archy(obj, '', { unicode })
1031cb0ef41Sopenharmony_ci
1041cb0ef41Sopenharmony_ci    const result = depth({
1051cb0ef41Sopenharmony_ci      tree: fundingInfo,
1061cb0ef41Sopenharmony_ci
1071cb0ef41Sopenharmony_ci      // composes human readable package name
1081cb0ef41Sopenharmony_ci      // and creates a new archy item for readable output
1091cb0ef41Sopenharmony_ci      visit: ({ name, version, funding }) => {
1101cb0ef41Sopenharmony_ci        const [fundingSource] = [].concat(normalizeFunding(funding)).filter(isValidFunding)
1111cb0ef41Sopenharmony_ci        const { url } = fundingSource || {}
1121cb0ef41Sopenharmony_ci        const pkgRef = getPrintableName({ name, version })
1131cb0ef41Sopenharmony_ci        let item = {
1141cb0ef41Sopenharmony_ci          label: pkgRef,
1151cb0ef41Sopenharmony_ci        }
1161cb0ef41Sopenharmony_ci
1171cb0ef41Sopenharmony_ci        if (url) {
1181cb0ef41Sopenharmony_ci          item.label = tree({
1191cb0ef41Sopenharmony_ci            label: this.npm.chalk.bgBlack.white(url),
1201cb0ef41Sopenharmony_ci            nodes: [pkgRef],
1211cb0ef41Sopenharmony_ci          }).trim()
1221cb0ef41Sopenharmony_ci
1231cb0ef41Sopenharmony_ci          // stacks all packages together under the same item
1241cb0ef41Sopenharmony_ci          if (seenUrls.has(url)) {
1251cb0ef41Sopenharmony_ci            item = seenUrls.get(url)
1261cb0ef41Sopenharmony_ci            item.label += `, ${pkgRef}`
1271cb0ef41Sopenharmony_ci            return null
1281cb0ef41Sopenharmony_ci          } else {
1291cb0ef41Sopenharmony_ci            seenUrls.set(url, item)
1301cb0ef41Sopenharmony_ci          }
1311cb0ef41Sopenharmony_ci        }
1321cb0ef41Sopenharmony_ci
1331cb0ef41Sopenharmony_ci        return item
1341cb0ef41Sopenharmony_ci      },
1351cb0ef41Sopenharmony_ci
1361cb0ef41Sopenharmony_ci      // puts child nodes back into returned archy
1371cb0ef41Sopenharmony_ci      // output while also filtering out missing items
1381cb0ef41Sopenharmony_ci      leave: (item, children) => {
1391cb0ef41Sopenharmony_ci        if (item) {
1401cb0ef41Sopenharmony_ci          item.nodes = children.filter(Boolean)
1411cb0ef41Sopenharmony_ci        }
1421cb0ef41Sopenharmony_ci
1431cb0ef41Sopenharmony_ci        return item
1441cb0ef41Sopenharmony_ci      },
1451cb0ef41Sopenharmony_ci
1461cb0ef41Sopenharmony_ci      // turns tree-like object return by libnpmfund
1471cb0ef41Sopenharmony_ci      // into children to be properly read by treeverse
1481cb0ef41Sopenharmony_ci      getChildren: node =>
1491cb0ef41Sopenharmony_ci        Object.keys(node.dependencies || {}).map(key => ({
1501cb0ef41Sopenharmony_ci          name: key,
1511cb0ef41Sopenharmony_ci          ...node.dependencies[key],
1521cb0ef41Sopenharmony_ci        })),
1531cb0ef41Sopenharmony_ci    })
1541cb0ef41Sopenharmony_ci
1551cb0ef41Sopenharmony_ci    const res = tree(result)
1561cb0ef41Sopenharmony_ci    return this.npm.chalk.reset(res)
1571cb0ef41Sopenharmony_ci  }
1581cb0ef41Sopenharmony_ci
1591cb0ef41Sopenharmony_ci  async openFundingUrl ({ path, tree, spec, fundingSourceNumber }) {
1601cb0ef41Sopenharmony_ci    const arg = npa(spec, path)
1611cb0ef41Sopenharmony_ci
1621cb0ef41Sopenharmony_ci    const retrievePackageMetadata = () => {
1631cb0ef41Sopenharmony_ci      if (arg.type === 'directory') {
1641cb0ef41Sopenharmony_ci        if (tree.path === arg.fetchSpec) {
1651cb0ef41Sopenharmony_ci          // matches cwd, e.g: npm fund .
1661cb0ef41Sopenharmony_ci          return tree.package
1671cb0ef41Sopenharmony_ci        } else {
1681cb0ef41Sopenharmony_ci          // matches any file path within current arborist inventory
1691cb0ef41Sopenharmony_ci          for (const item of tree.inventory.values()) {
1701cb0ef41Sopenharmony_ci            if (item.path === arg.fetchSpec) {
1711cb0ef41Sopenharmony_ci              return item.package
1721cb0ef41Sopenharmony_ci            }
1731cb0ef41Sopenharmony_ci          }
1741cb0ef41Sopenharmony_ci        }
1751cb0ef41Sopenharmony_ci      } else {
1761cb0ef41Sopenharmony_ci        // tries to retrieve a package from arborist inventory
1771cb0ef41Sopenharmony_ci        // by matching resulted package name from the provided spec
1781cb0ef41Sopenharmony_ci        const [item] = [...tree.inventory.query('name', arg.name)]
1791cb0ef41Sopenharmony_ci          .filter(i => semver.valid(i.package.version))
1801cb0ef41Sopenharmony_ci          .sort((a, b) => semver.rcompare(a.package.version, b.package.version))
1811cb0ef41Sopenharmony_ci
1821cb0ef41Sopenharmony_ci        if (item) {
1831cb0ef41Sopenharmony_ci          return item.package
1841cb0ef41Sopenharmony_ci        }
1851cb0ef41Sopenharmony_ci      }
1861cb0ef41Sopenharmony_ci    }
1871cb0ef41Sopenharmony_ci
1881cb0ef41Sopenharmony_ci    const { funding } =
1891cb0ef41Sopenharmony_ci      retrievePackageMetadata() ||
1901cb0ef41Sopenharmony_ci      (await pacote.manifest(arg, this.npm.flatOptions).catch(() => ({})))
1911cb0ef41Sopenharmony_ci
1921cb0ef41Sopenharmony_ci    const validSources = [].concat(normalizeFunding(funding)).filter(isValidFunding)
1931cb0ef41Sopenharmony_ci
1941cb0ef41Sopenharmony_ci    if (!validSources.length) {
1951cb0ef41Sopenharmony_ci      throw errCode(`No valid funding method available for: ${spec}`, 'ENOFUND')
1961cb0ef41Sopenharmony_ci    }
1971cb0ef41Sopenharmony_ci
1981cb0ef41Sopenharmony_ci    const fundSource = fundingSourceNumber
1991cb0ef41Sopenharmony_ci      ? validSources[fundingSourceNumber - 1]
2001cb0ef41Sopenharmony_ci      : validSources.length === 1 ? validSources[0]
2011cb0ef41Sopenharmony_ci      : null
2021cb0ef41Sopenharmony_ci
2031cb0ef41Sopenharmony_ci    if (fundSource) {
2041cb0ef41Sopenharmony_ci      return openUrl(this.npm, ...this.urlMessage(fundSource))
2051cb0ef41Sopenharmony_ci    }
2061cb0ef41Sopenharmony_ci
2071cb0ef41Sopenharmony_ci    const ambiguousUrlMsg = [
2081cb0ef41Sopenharmony_ci      ...validSources.map((s, i) => `${i + 1}: ${this.urlMessage(s).reverse().join(': ')}`),
2091cb0ef41Sopenharmony_ci      `Run ${this.usageMessage({ which: '1' })}` +
2101cb0ef41Sopenharmony_ci      ', for example, to open the first funding URL listed in that package',
2111cb0ef41Sopenharmony_ci    ]
2121cb0ef41Sopenharmony_ci    if (fundingSourceNumber) {
2131cb0ef41Sopenharmony_ci      ambiguousUrlMsg.unshift(`--which=${fundingSourceNumber} is not a valid index`)
2141cb0ef41Sopenharmony_ci    }
2151cb0ef41Sopenharmony_ci    this.npm.output(ambiguousUrlMsg.join('\n'))
2161cb0ef41Sopenharmony_ci  }
2171cb0ef41Sopenharmony_ci
2181cb0ef41Sopenharmony_ci  urlMessage (source) {
2191cb0ef41Sopenharmony_ci    const { type, url } = source
2201cb0ef41Sopenharmony_ci    const typePrefix = type ? `${type} funding` : 'Funding'
2211cb0ef41Sopenharmony_ci    const message = `${typePrefix} available at the following URL`
2221cb0ef41Sopenharmony_ci    return [url, message]
2231cb0ef41Sopenharmony_ci  }
2241cb0ef41Sopenharmony_ci}
2251cb0ef41Sopenharmony_cimodule.exports = Fund
226