Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

test: add lib/link.js tests #1786

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
104 changes: 87 additions & 17 deletions lib/link.js
@@ -1,14 +1,18 @@
// link with no args: symlink the folder to the global location
// link with package arg: symlink the global to the local
'use strict'

const { readdir } = require('fs')
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 npm = require('./npm.js')
const usageUtil = require('./utils/usage.js')
const reifyOutput = require('./utils/reify-output.js')
const { resolve } = require('path')
const Arborist = require('@npmcli/arborist')

const completion = (opts, cb) => {
const { readdir } = require('fs')
const dir = npm.globalDir
readdir(dir, (er, files) => cb(er, files.filter(f => !/^[._-]/.test(f))))
}
Expand All @@ -23,44 +27,110 @@ const cmd = (args, cb) => link(args).then(() => cb()).catch(cb)

const link = async args => {
if (npm.config.get('global')) {
throw new Error(
'link should never be --global.\n' +
'Please re-run this command with --local'
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) !== npm.prefix)
return args.length ? linkInstall(args) : linkPkg()
return args.length
? linkInstall(args)
: linkPkg()
}

// Returns a list of items that can't be fulfilled by
// things found in the current arborist inventory
const missingArgsFromTree = (tree, 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 &&
!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
}

const linkInstall = async args => {
// add all the args as global installs, and then add symlink installs locally
// to the packages in the global space.
// load current packages from the global space,
// and then add symlinks installs locally
const globalTop = resolve(npm.globalDir, '..')
const globalArb = new Arborist({
const globalOpts = {
...npm.flatOptions,
path: globalTop,
global: true
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)
})

const globals = await globalArb.reify({ add: args })
// any extra arg that is missing from the current
// global space should be reified there first
const missing = 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
)
}

const links = globals.edgesOut.keys()
// create a new arborist instance for the local prefix and
// reify all the pending names as symlinks there
const localArb = new Arborist({
...npm.flatOptions,
path: npm.prefix
})
await localArb.reify({
add: links.map(l => `file:${resolve(globalTop, 'node_modules', l)}`)
add: names.map(l => `file:${resolve(globalTop, 'node_modules', l)}`)
})

reifyOutput(localArb)
}

const linkPkg = async () => {
const globalTop = resolve(npm.globalDir, '..')
const arb = new Arborist({
...npm.flatOptions,
path: resolve(npm.globalDir, '..'),
path: globalTop,
global: true
})
await arb.reify({ add: [`file:${npm.prefix}`] })
Expand Down
3 changes: 2 additions & 1 deletion node_modules/@npmcli/arborist/lib/add-rm-pkg-deps.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

18 changes: 15 additions & 3 deletions node_modules/@npmcli/arborist/lib/arborist/rebuild.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion node_modules/@npmcli/arborist/package.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

14 changes: 7 additions & 7 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Expand Up @@ -42,7 +42,7 @@
"./package.json": "./package.json"
},
"dependencies": {
"@npmcli/arborist": "^0.0.21",
"@npmcli/arborist": "^0.0.23",
"@npmcli/ci-detect": "^1.2.0",
"@npmcli/run-script": "^1.5.0",
"abbrev": "~1.1.1",
Expand Down
25 changes: 25 additions & 0 deletions tap-snapshots/test-lib-link.js-TAP.test.js
@@ -0,0 +1,25 @@
/* IMPORTANT
* This snapshot file is auto-generated, but designed for humans.
* It should be checked into source control and tracked carefully.
* Re-generate by setting TAP_SNAPSHOT=1 and running tests.
* Make sure to inspect the output below. Do not ignore changes!
*/
'use strict'
exports[`test/lib/link.js TAP link global linked pkg to local nm when using args > should create a local symlink to global pkg 1`] = `
{CWD}/test/lib/link-link-global-linked-pkg-to-local-nm-when-using-args/my-project/node_modules/@myscope/bar -> {CWD}/test/lib/link-link-global-linked-pkg-to-local-nm-when-using-args/global-prefix/lib/node_modules/@myscope/bar
{CWD}/test/lib/link-link-global-linked-pkg-to-local-nm-when-using-args/my-project/node_modules/@myscope/linked -> {CWD}/test/lib/link-link-global-linked-pkg-to-local-nm-when-using-args/scoped-linked
{CWD}/test/lib/link-link-global-linked-pkg-to-local-nm-when-using-args/my-project/node_modules/a -> {CWD}/test/lib/link-link-global-linked-pkg-to-local-nm-when-using-args/global-prefix/lib/node_modules/a
{CWD}/test/lib/link-link-global-linked-pkg-to-local-nm-when-using-args/my-project/node_modules/link-me-too -> {CWD}/test/lib/link-link-global-linked-pkg-to-local-nm-when-using-args/link-me-too
{CWD}/test/lib/link-link-global-linked-pkg-to-local-nm-when-using-args/my-project/node_modules/test-pkg-link -> {CWD}/test/lib/link-link-global-linked-pkg-to-local-nm-when-using-args/test-pkg-link

`

exports[`test/lib/link.js TAP link pkg already in global space > should create a local symlink to global pkg 1`] = `
{CWD}/test/lib/link-link-pkg-already-in-global-space/my-project/node_modules/@myscope/linked -> {CWD}/test/lib/link-link-pkg-already-in-global-space/scoped-linked

`

exports[`test/lib/link.js TAP link to globalDir when in current working dir of pkg and no args > should create a global link to current pkg 1`] = `
{CWD}/test/lib/link-link-to-globalDir-when-in-current-working-dir-of-pkg-and-no-args/global-prefix/lib/node_modules/test-pkg-link -> {CWD}/test/lib/link-link-to-globalDir-when-in-current-working-dir-of-pkg-and-no-args/test-pkg-link

`