const fs = require('fs')
const util = require('util')
const readdir = util.promisify(fs.readdir)
const { resolve } = require('path')
const Arborist = require('@npmcli/arborist')
const npa = require('npm-package-arg')
const rpj = require('read-package-json-fast')
const semver = require('semver')
const reifyFinish = require('../utils/reify-finish.js')
const ArboristWorkspaceCmd = require('../arborist-cmd.js')
class Link extends ArboristWorkspaceCmd {
static description = 'Symlink a package folder'
static name = 'link'
static usage = [
'[<package-spec>]',
]
static params = [
'save',
'save-exact',
'global',
'global-style',
'legacy-bundling',
'strict-peer-deps',
'package-lock',
'omit',
'ignore-scripts',
'audit',
'bin-links',
'fund',
'dry-run',
...super.params,
]
async completion (opts) {
const dir = this.npm.globalDir
const files = await readdir(dir)
return files.filter(f => !/^[._-]/.test(f))
}
async exec (args) {
if (this.npm.global) {
throw Object.assign(
new Error(
'link should never be --global.\n' +
'Please re-run this command with --local'
),
{ code: 'ELINKGLOBAL' }
)
}
// link with no args: symlink the folder to the global location
// link with package arg: symlink the global to the local
args = args.filter(a => resolve(a) !== this.npm.prefix)
return args.length
? this.linkInstall(args)
: this.linkPkg()
}
async linkInstall (args) {
// load current packages from the global space,
// and then add symlinks installs locally
const globalTop = resolve(this.npm.globalDir, '..')
const globalOpts = {
...this.npm.flatOptions,
path: globalTop,
global: true,
prune: false,
}
const globalArb = new Arborist(globalOpts)
// get only current top-level packages from the global space
const globals = await globalArb.loadActual({
filter: (node, kid) =>
!node.isRoot || args.some(a => npa(a).name === kid),
})
// any extra arg that is missing from the current
// global space should be reified there first
const missing = this.missingArgsFromTree(globals, args)
if (missing.length) {
await globalArb.reify({
...globalOpts,
add: missing,
})
}
// get a list of module names that should be linked in the local prefix
const names = []
for (const a of args) {
const arg = npa(a)
names.push(
arg.type === 'directory'
? (await rpj(resolve(arg.fetchSpec, 'package.json'))).name
: arg.name
)
}
// npm link should not save=true by default unless you're
// using any of --save-dev or other types
const save =
Boolean(
this.npm.config.find('save') !== 'default' ||
this.npm.config.get('save-optional') ||
this.npm.config.get('save-peer') ||
this.npm.config.get('save-dev') ||
this.npm.config.get('save-prod')
)
// create a new arborist instance for the local prefix and
// reify all the pending names as symlinks there
const localArb = new Arborist({
...this.npm.flatOptions,
prune: false,
path: this.npm.prefix,
save,
})
await localArb.reify({
...this.npm.flatOptions,
prune: false,
path: this.npm.prefix,
add: names.map(l => `file:${resolve(globalTop, 'node_modules', l).replace(/#/g, '%23')}`),
save,
workspaces: this.workspaceNames,
})
await reifyFinish(this.npm, localArb)
}
async linkPkg () {
const wsp = this.workspacePaths
const paths = wsp && wsp.length ? wsp : [this.npm.prefix]
const add = paths.map(path => `file:${path.replace(/#/g, '%23')}`)
const globalTop = resolve(this.npm.globalDir, '..')
const arb = new Arborist({
...this.npm.flatOptions,
path: globalTop,
global: true,
})
await arb.reify({
add,
})
await reifyFinish(this.npm, arb)
}
// Returns a list of items that can't be fulfilled by
// things found in the current arborist inventory
missingArgsFromTree (tree, args) {
if (tree.isLink) {
return this.missingArgsFromTree(tree.target, args)
}
const foundNodes = []
const missing = args.filter(a => {
const arg = npa(a)
const nodes = tree.children.values()
const argFound = [...nodes].every(node => {
// TODO: write tests for unmatching version specs, this is hard to test
// atm but should be simple once we have a mocked registry again
if (arg.name !== node.name /* istanbul ignore next */ || (
arg.version &&
/* istanbul ignore next */
!semver.satisfies(node.version, arg.version)
)) {
foundNodes.push(node)
return true
}
})
return argFound
})
// remote nodes from the loaded tree in order
// to avoid dropping them later when reifying
for (const node of foundNodes) {
node.parent = null
}
return missing
}
}
module.exports = Link
| Name | Type | Size | Permission | Actions |
|---|---|---|---|---|
| access.js | File | 5.45 KB | 0644 |
|
| adduser.js | File | 2.2 KB | 0644 |
|
| audit.js | File | 11.95 KB | 0644 |
|
| bin.js | File | 729 B | 0644 |
|
| birthday.js | File | 508 B | 0644 |
|
| bugs.js | File | 815 B | 0644 |
|
| cache.js | File | 7.08 KB | 0644 |
|
| ci.js | File | 3.63 KB | 0644 |
|
| completion.js | File | 8.91 KB | 0644 |
|
| config.js | File | 8.11 KB | 0644 |
|
| dedupe.js | File | 1.37 KB | 0644 |
|
| deprecate.js | File | 2.06 KB | 0644 |
|
| diff.js | File | 8.1 KB | 0644 |
|
| dist-tag.js | File | 5.47 KB | 0644 |
|
| docs.js | File | 447 B | 0644 |
|
| doctor.js | File | 9.22 KB | 0644 |
|
| edit.js | File | 2 KB | 0644 |
|
| exec.js | File | 2.44 KB | 0644 |
|
| explain.js | File | 3.55 KB | 0644 |
|
| explore.js | File | 2.33 KB | 0644 |
|
| find-dupes.js | File | 602 B | 0644 |
|
| fund.js | File | 6.37 KB | 0644 |
|
| get.js | File | 524 B | 0644 |
|
| help-search.js | File | 5.62 KB | 0644 |
|
| help.js | File | 4.53 KB | 0644 |
|
| hook.js | File | 3.93 KB | 0644 |
|
| init.js | File | 6.81 KB | 0644 |
|
| install-ci-test.js | File | 377 B | 0644 |
|
| install-test.js | File | 374 B | 0644 |
|
| install.js | File | 5.11 KB | 0644 |
|
| link.js | File | 5.02 KB | 0644 |
|
| ll.js | File | 234 B | 0644 |
|
| logout.js | File | 1.34 KB | 0644 |
|
| ls.js | File | 16.94 KB | 0644 |
|
| org.js | File | 4.2 KB | 0644 |
|
| outdated.js | File | 8.84 KB | 0644 |
|
| owner.js | File | 5.88 KB | 0644 |
|
| pack.js | File | 2.36 KB | 0644 |
|
| ping.js | File | 874 B | 0644 |
|
| pkg.js | File | 3.47 KB | 0644 |
|
| prefix.js | File | 343 B | 0644 |
|
| profile.js | File | 11.25 KB | 0644 |
|
| prune.js | File | 779 B | 0644 |
|
| publish.js | File | 6.33 KB | 0644 |
|
| query.js | File | 2.81 KB | 0644 |
|
| rebuild.js | File | 2.16 KB | 0644 |
|
| repo.js | File | 1.24 KB | 0644 |
|
| restart.js | File | 351 B | 0644 |
|
| root.js | File | 298 B | 0644 |
|
| run-script.js | File | 6.9 KB | 0644 |
|
| search.js | File | 2.72 KB | 0644 |
|
| set-script.js | File | 2.63 KB | 0644 |
|
| set.js | File | 572 B | 0644 |
|
| shrinkwrap.js | File | 2.64 KB | 0644 |
|
| star.js | File | 1.87 KB | 0644 |
|
| stars.js | File | 1.03 KB | 0644 |
|
| start.js | File | 341 B | 0644 |
|
| stop.js | File | 336 B | 0644 |
|
| team.js | File | 4.44 KB | 0644 |
|
| test.js | File | 336 B | 0644 |
|
| token.js | File | 6.79 KB | 0644 |
|
| uninstall.js | File | 1.52 KB | 0644 |
|
| unpublish.js | File | 4.51 KB | 0644 |
|
| unstar.js | File | 182 B | 0644 |
|
| update.js | File | 1.7 KB | 0644 |
|
| version.js | File | 3.6 KB | 0644 |
|
| view.js | File | 14.38 KB | 0644 |
|
| whoami.js | File | 514 B | 0644 |
|