Skip to content

Commit 2019abd

Browse files
committed
test: add lib/link.js tests
- 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
1 parent 3743a42 commit 2019abd

File tree

3 files changed

+266
-16
lines changed

3 files changed

+266
-16
lines changed

lib/link.js

Lines changed: 29 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,66 +1,79 @@
1-
// link with no args: symlink the folder to the global location
2-
// link with package arg: symlink the global to the local
1+
'use strict'
2+
3+
const { readdir } = require('fs')
4+
const { resolve } = require('path')
5+
6+
const Arborist = require('@npmcli/arborist')
37

48
const npm = require('./npm.js')
59
const usageUtil = require('./utils/usage.js')
610
const reifyOutput = require('./utils/reify-output.js')
7-
const { resolve } = require('path')
8-
const Arborist = require('@npmcli/arborist')
911

1012
const completion = (opts, cb) => {
11-
const { readdir } = require('fs')
1213
const dir = npm.globalDir
1314
readdir(dir, (er, files) => cb(er, files.filter(f => !/^[._-]/.test(f))))
1415
}
1516

1617
const usage = usageUtil(
1718
'link',
1819
'npm link (in package dir)' +
19-
'\nnpm link [<@scope>/]<pkg>[@<version>]'
20+
'\nnpm link [<@scope>/]<pkg>'
2021
)
2122

2223
const cmd = (args, cb) => link(args).then(() => cb()).catch(cb)
2324

2425
const link = async args => {
2526
if (npm.config.get('global')) {
26-
throw new Error(
27-
'link should never be --global.\n' +
28-
'Please re-run this command with --local'
27+
throw Object.assign(
28+
new Error(
29+
'link should never be --global.\n' +
30+
'Please re-run this command with --local'
31+
),
32+
{ code: 'ELINKGLOBAL' }
2933
)
3034
}
3135

36+
// link with no args: symlink the folder to the global location
37+
// link with package arg: symlink the global to the local
3238
args = args.filter(a => resolve(a) !== npm.prefix)
33-
return args.length ? linkInstall(args) : linkPkg()
39+
return args.length
40+
? linkInstall(args)
41+
: linkPkg()
3442
}
3543

3644
const linkInstall = async args => {
37-
// add all the args as global installs, and then add symlink installs locally
38-
// to the packages in the global space.
45+
// load current packages from the global space,
46+
// and then add symlinks installs locally
3947
const globalTop = resolve(npm.globalDir, '..')
4048
const globalArb = new Arborist({
4149
...npm.flatOptions,
4250
path: globalTop,
4351
global: true
4452
})
4553

46-
const globals = await globalArb.reify({ add: args })
54+
const globals = await globalArb.loadActual()
55+
56+
const links = [
57+
...globals.children.values()
58+
]
59+
.filter(i => args.some(j => j === i.name))
4760

48-
const links = globals.edgesOut.keys()
4961
const localArb = new Arborist({
5062
...npm.flatOptions,
5163
path: npm.prefix
5264
})
5365
await localArb.reify({
54-
add: links.map(l => `file:${resolve(globalTop, 'node_modules', l)}`)
66+
add: links.map(l => `file:${resolve(globalTop, 'node_modules', l.path)}`)
5567
})
5668

5769
reifyOutput(localArb)
5870
}
5971

6072
const linkPkg = async () => {
73+
const globalTop = resolve(npm.globalDir, '..')
6174
const arb = new Arborist({
6275
...npm.flatOptions,
63-
path: resolve(npm.globalDir, '..'),
76+
path: globalTop,
6477
global: true
6578
})
6679
await arb.reify({ add: [`file:${npm.prefix}`] })
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
/* IMPORTANT
2+
* This snapshot file is auto-generated, but designed for humans.
3+
* It should be checked into source control and tracked carefully.
4+
* Re-generate by setting TAP_SNAPSHOT=1 and running tests.
5+
* Make sure to inspect the output below. Do not ignore changes!
6+
*/
7+
'use strict'
8+
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`] = `
9+
{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
10+
{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
11+
{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
12+
{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
13+
14+
`
15+
16+
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`] = `
17+
{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
18+
19+
`

test/lib/link.js

Lines changed: 218 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,218 @@
1+
const { resolve } = require('path')
2+
3+
const Arborist = require('@npmcli/arborist')
4+
const t = require('tap')
5+
const requireInject = require('require-inject')
6+
7+
const redactCwd = (path) => {
8+
const normalizePath = p => p
9+
.replace(/\\+/g, '/')
10+
.replace(/\r\n/g, '\n')
11+
return normalizePath(path)
12+
.replace(new RegExp(normalizePath(process.cwd()), 'g'), '{CWD}')
13+
}
14+
15+
t.cleanSnapshot = (str) => redactCwd(str)
16+
17+
let reifyOutput
18+
const npm = {
19+
globalDir: null,
20+
prefix: null,
21+
flatOptions: {},
22+
config: {
23+
get () { return false }
24+
}
25+
}
26+
const printLinks = async (opts) => {
27+
let res = ''
28+
const arb = new Arborist(opts)
29+
const tree = await arb.loadActual()
30+
const linkedItems = [...tree.inventory.values()]
31+
for (const item of linkedItems) {
32+
if (item.target)
33+
res += `${item.path} -> ${item.target.path}\n`
34+
}
35+
return res
36+
}
37+
38+
const mocks = {
39+
'../../lib/npm.js': npm,
40+
'../../lib/utils/reify-output.js': () => reifyOutput()
41+
}
42+
43+
const link = requireInject('../../lib/link.js', mocks)
44+
45+
t.test('link to globalDir when in current working dir of pkg and no args', (t) => {
46+
t.plan(2)
47+
48+
const testdir = t.testdir({
49+
'global-prefix': {
50+
lib: {
51+
node_modules: {
52+
a: {
53+
'package.json': JSON.stringify({
54+
name: 'a',
55+
version: '1.0.0'
56+
})
57+
}
58+
}
59+
}
60+
},
61+
'test-pkg-link': {
62+
'package.json': JSON.stringify({
63+
name: 'test-pkg-link',
64+
version: '1.0.0'
65+
})
66+
}
67+
})
68+
npm.globalDir = resolve(testdir, 'global-prefix', 'lib', 'node_modules')
69+
npm.prefix = resolve(testdir, 'test-pkg-link')
70+
71+
reifyOutput = async () => {
72+
reifyOutput = undefined
73+
74+
const links = await printLinks({
75+
path: resolve(npm.globalDir, '..'),
76+
global: true
77+
})
78+
79+
t.matchSnapshot(links, 'should create a global link to current pkg')
80+
}
81+
82+
link([], (err) => {
83+
t.ifError(err, 'should not error out')
84+
})
85+
})
86+
87+
t.test('link global linked pkg to local nm when using args', (t) => {
88+
t.plan(2)
89+
90+
const testdir = t.testdir({
91+
'global-prefix': {
92+
lib: {
93+
node_modules: {
94+
'@myscope': {
95+
foo: {
96+
'package.json': JSON.stringify({
97+
name: '@myscope/foo',
98+
version: '1.0.0'
99+
})
100+
},
101+
bar: {
102+
'package.json': JSON.stringify({
103+
name: '@myscope/bar',
104+
version: '1.0.0'
105+
})
106+
},
107+
linked: t.fixture('symlink', '../../../../scoped-linked')
108+
},
109+
a: {
110+
'package.json': JSON.stringify({
111+
name: 'a',
112+
version: '1.0.0'
113+
})
114+
},
115+
b: {
116+
'package.json': JSON.stringify({
117+
name: 'b',
118+
version: '1.0.0'
119+
})
120+
},
121+
'test-pkg-link': t.fixture('symlink', '../../../test-pkg-link')
122+
}
123+
}
124+
},
125+
'test-pkg-link': {
126+
'package.json': JSON.stringify({
127+
name: 'test-pkg-link',
128+
version: '1.0.0'
129+
})
130+
},
131+
'scoped-linked': {
132+
'package.json': JSON.stringify({
133+
name: '@myscope/linked',
134+
version: '1.0.0'
135+
})
136+
},
137+
'my-project': {
138+
'package.json': JSON.stringify({
139+
name: 'my-project',
140+
version: '1.0.0',
141+
dependencies: {
142+
foo: '^1.0.0'
143+
}
144+
}),
145+
node_modules: {
146+
foo: {
147+
'package.json': JSON.stringify({
148+
name: 'foo',
149+
version: '1.0.0'
150+
})
151+
}
152+
}
153+
}
154+
})
155+
npm.globalDir = resolve(testdir, 'global-prefix', 'lib', 'node_modules')
156+
npm.prefix = resolve(testdir, 'my-project')
157+
158+
reifyOutput = async () => {
159+
reifyOutput = undefined
160+
161+
const links = await printLinks({
162+
path: npm.prefix
163+
})
164+
165+
t.matchSnapshot(links, 'should create a local symlink to global pkg')
166+
}
167+
168+
// installs examples for:
169+
// - test-pkg-link: pkg linked to globalDir from local fs
170+
// - @myscope/linked: scoped pkg linked to globalDir from local fs
171+
// - @myscope/bar: prev installed scoped package available in globalDir
172+
// - a: prev installed package available in globalDir
173+
link(['test-pkg-link', '@myscope/linked', '@myscope/bar', 'a'], (err) => {
174+
t.ifError(err, 'should not error out')
175+
})
176+
})
177+
178+
t.test('completion', (t) => {
179+
const testdir = t.testdir({
180+
'global-prefix': {
181+
lib: {
182+
node_modules: {
183+
foo: {},
184+
bar: {},
185+
lorem: {},
186+
ipsum: {}
187+
}
188+
}
189+
}
190+
})
191+
npm.globalDir = resolve(testdir, 'global-prefix', 'lib', 'node_modules')
192+
193+
link.completion({}, (err, words) => {
194+
t.ifError(err, 'should not error out')
195+
t.deepEqual(
196+
words,
197+
['bar', 'foo', 'ipsum', 'lorem'],
198+
'should list all package names available in globalDir'
199+
)
200+
t.end()
201+
})
202+
})
203+
204+
t.test('--global option', (t) => {
205+
const _config = npm.config
206+
npm.config = { get () { return true } }
207+
link([], (err) => {
208+
npm.config = _config
209+
210+
t.match(
211+
err.message,
212+
/link should never be --global/,
213+
'should throw an useful error'
214+
)
215+
216+
t.end()
217+
})
218+
})

0 commit comments

Comments
 (0)