Skip to content

Commit

Permalink
test: add lib/link.js tests
Browse files Browse the repository at this point in the history
- Added ELINKGLOBAL error code for when using --global
- Added tests for lib/link.js
- Do not reify, only load globals when linking a pkg to a local prefix
- Fixes: #1777
  • Loading branch information
ruyadorno committed Sep 16, 2020
1 parent 3743a42 commit 2019abd
Show file tree
Hide file tree
Showing 3 changed files with 266 additions and 16 deletions.
45 changes: 29 additions & 16 deletions lib/link.js
Original file line number Diff line number Diff line change
@@ -1,66 +1,79 @@
// 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 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))))
}

const usage = usageUtil(
'link',
'npm link (in package dir)' +
'\nnpm link [<@scope>/]<pkg>[@<version>]'
'\nnpm link [<@scope>/]<pkg>'
)

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()
}

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({
...npm.flatOptions,
path: globalTop,
global: true
})

const globals = await globalArb.reify({ add: args })
const globals = await globalArb.loadActual()

const links = [
...globals.children.values()
]
.filter(i => args.some(j => j === i.name))

const links = globals.edgesOut.keys()
const localArb = new Arborist({
...npm.flatOptions,
path: npm.prefix
})
await localArb.reify({
add: links.map(l => `file:${resolve(globalTop, 'node_modules', l)}`)
add: links.map(l => `file:${resolve(globalTop, 'node_modules', l.path)}`)
})

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
19 changes: 19 additions & 0 deletions tap-snapshots/test-lib-link.js-TAP.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
/* 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/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/@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/test-pkg-link -> {CWD}/test/lib/link-link-global-linked-pkg-to-local-nm-when-using-args/test-pkg-link
{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
`

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
`
218 changes: 218 additions & 0 deletions test/lib/link.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,218 @@
const { resolve } = require('path')

const Arborist = require('@npmcli/arborist')
const t = require('tap')
const requireInject = require('require-inject')

const redactCwd = (path) => {
const normalizePath = p => p
.replace(/\\+/g, '/')
.replace(/\r\n/g, '\n')
return normalizePath(path)
.replace(new RegExp(normalizePath(process.cwd()), 'g'), '{CWD}')
}

t.cleanSnapshot = (str) => redactCwd(str)

let reifyOutput
const npm = {
globalDir: null,
prefix: null,
flatOptions: {},
config: {
get () { return false }
}
}
const printLinks = async (opts) => {
let res = ''
const arb = new Arborist(opts)
const tree = await arb.loadActual()
const linkedItems = [...tree.inventory.values()]
for (const item of linkedItems) {
if (item.target)
res += `${item.path} -> ${item.target.path}\n`
}
return res
}

const mocks = {
'../../lib/npm.js': npm,
'../../lib/utils/reify-output.js': () => reifyOutput()
}

const link = requireInject('../../lib/link.js', mocks)

t.test('link to globalDir when in current working dir of pkg and no args', (t) => {
t.plan(2)

const testdir = t.testdir({
'global-prefix': {
lib: {
node_modules: {
a: {
'package.json': JSON.stringify({
name: 'a',
version: '1.0.0'
})
}
}
}
},
'test-pkg-link': {
'package.json': JSON.stringify({
name: 'test-pkg-link',
version: '1.0.0'
})
}
})
npm.globalDir = resolve(testdir, 'global-prefix', 'lib', 'node_modules')
npm.prefix = resolve(testdir, 'test-pkg-link')

reifyOutput = async () => {
reifyOutput = undefined

const links = await printLinks({
path: resolve(npm.globalDir, '..'),
global: true
})

t.matchSnapshot(links, 'should create a global link to current pkg')
}

link([], (err) => {
t.ifError(err, 'should not error out')
})
})

t.test('link global linked pkg to local nm when using args', (t) => {
t.plan(2)

const testdir = t.testdir({
'global-prefix': {
lib: {
node_modules: {
'@myscope': {
foo: {
'package.json': JSON.stringify({
name: '@myscope/foo',
version: '1.0.0'
})
},
bar: {
'package.json': JSON.stringify({
name: '@myscope/bar',
version: '1.0.0'
})
},
linked: t.fixture('symlink', '../../../../scoped-linked')
},
a: {
'package.json': JSON.stringify({
name: 'a',
version: '1.0.0'
})
},
b: {
'package.json': JSON.stringify({
name: 'b',
version: '1.0.0'
})
},
'test-pkg-link': t.fixture('symlink', '../../../test-pkg-link')
}
}
},
'test-pkg-link': {
'package.json': JSON.stringify({
name: 'test-pkg-link',
version: '1.0.0'
})
},
'scoped-linked': {
'package.json': JSON.stringify({
name: '@myscope/linked',
version: '1.0.0'
})
},
'my-project': {
'package.json': JSON.stringify({
name: 'my-project',
version: '1.0.0',
dependencies: {
foo: '^1.0.0'
}
}),
node_modules: {
foo: {
'package.json': JSON.stringify({
name: 'foo',
version: '1.0.0'
})
}
}
}
})
npm.globalDir = resolve(testdir, 'global-prefix', 'lib', 'node_modules')
npm.prefix = resolve(testdir, 'my-project')

reifyOutput = async () => {
reifyOutput = undefined

const links = await printLinks({
path: npm.prefix
})

t.matchSnapshot(links, 'should create a local symlink to global pkg')
}

// installs examples for:
// - test-pkg-link: pkg linked to globalDir from local fs
// - @myscope/linked: scoped pkg linked to globalDir from local fs
// - @myscope/bar: prev installed scoped package available in globalDir
// - a: prev installed package available in globalDir
link(['test-pkg-link', '@myscope/linked', '@myscope/bar', 'a'], (err) => {
t.ifError(err, 'should not error out')
})
})

t.test('completion', (t) => {
const testdir = t.testdir({
'global-prefix': {
lib: {
node_modules: {
foo: {},
bar: {},
lorem: {},
ipsum: {}
}
}
}
})
npm.globalDir = resolve(testdir, 'global-prefix', 'lib', 'node_modules')

link.completion({}, (err, words) => {
t.ifError(err, 'should not error out')
t.deepEqual(
words,
['bar', 'foo', 'ipsum', 'lorem'],
'should list all package names available in globalDir'
)
t.end()
})
})

t.test('--global option', (t) => {
const _config = npm.config
npm.config = { get () { return true } }
link([], (err) => {
npm.config = _config

t.match(
err.message,
/link should never be --global/,
'should throw an useful error'
)

t.end()
})
})

0 comments on commit 2019abd

Please sign in to comment.