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