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