• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1'use strict'
2var path = require('path')
3var test = require('tap').test
4var fs = require('fs')
5var rimraf = require('rimraf')
6var mr = require('npm-registry-mock')
7var Tacks = require('tacks')
8var File = Tacks.File
9var Symlink = Tacks.Symlink
10var Dir = Tacks.Dir
11var common = require('../common-tap.js')
12var isWindows = require('../../lib/utils/is-windows.js')
13
14var basedir = common.pkg
15var testdir = path.join(basedir, 'testdir')
16var cachedir = common.cache
17var globaldir = path.join(basedir, 'global')
18var tmpdir = path.join(basedir, 'tmp')
19
20var conf = {
21  cwd: testdir,
22  env: common.emptyEnv().extend({
23    npm_config_cache: cachedir,
24    npm_config_tmp: tmpdir,
25    npm_config_prefix: globaldir,
26    npm_config_registry: common.registry,
27    npm_config_loglevel: 'error'
28  }),
29  stdio: [0, 'pipe', 2]
30}
31var confE = {
32  cwd: testdir,
33  env: conf.env,
34  stdio: [0, 'pipe', 'pipe']
35}
36
37function readJson (file) {
38  return JSON.parse(fs.readFileSync(file))
39}
40
41function isSymlink (t, file, message) {
42  try {
43    var info = fs.lstatSync(file)
44    t.is(info.isSymbolicLink(), true, message)
45  } catch (ex) {
46    if (ex.code === 'ENOENT') {
47      t.fail(message, {found: null, wanted: 'symlink', compare: 'fs.lstat(' + file + ')'})
48    } else {
49      t.fail(message, {found: ex, wanted: 'symlink', compare: 'fs.lstat(' + file + ')'})
50    }
51  }
52}
53
54function fileExists (t, file, message) {
55  try {
56    fs.statSync(file)
57    t.pass(message)
58  } catch (ex) {
59    if (ex.code === 'ENOENT') {
60      t.fail(message, {found: null, wanted: 'exists', compare: 'fs.stat(' + file + ')'})
61    } else {
62      t.fail(message, {found: ex, wanted: 'exists', compare: 'fs.stat(' + file + ')'})
63    }
64  }
65}
66
67function noFileExists (t, file, message) {
68  try {
69    fs.statSync(file)
70    t.fail(message, {found: 'exists', wanted: 'not exists', compare: 'fs.stat(' + file + ')'})
71  } catch (ex) {
72    if (ex.code === 'ENOENT') {
73      t.pass(message)
74    } else {
75      t.fail(message, {found: ex, wanted: 'not exists', compare: 'fs.stat(' + file + ')'})
76    }
77  }
78}
79
80var server
81var testdirContent = {
82  node_modules: Dir({}),
83  pkga: Dir({
84    'package.json': File({
85      name: 'pkga',
86      version: '1.0.0'
87    })
88  }),
89  pkgb: Dir({
90    'package.json': File({
91      name: 'pkgb',
92      version: '1.0.0'
93    })
94  }),
95  pkgc: Dir({
96    'package.json': File({
97      name: 'pkgc',
98      version: '1.0.0'
99    })
100  }),
101  pkgd: Dir({
102    'package.json': File({
103      name: 'pkgd',
104      version: '1.0.0'
105    })
106  })
107}
108
109var fixture
110function setup () {
111  fixture = new Tacks(Dir({
112    cache: Dir(),
113    global: Dir(),
114    tmp: Dir(),
115    testdir: Dir(testdirContent)
116  }))
117  cleanup()
118  fixture.create(basedir)
119}
120
121function cleanup () {
122  fixture.remove(basedir)
123}
124
125test('setup', function (t) {
126  process.nextTick(function () {
127    setup()
128    mr({port: common.port, throwOnUnmatched: true}, function (err, s) {
129      if (err) throw err
130      server = s
131      t.done()
132    })
133  })
134})
135
136var installOk = []
137var slashes = [ 'unix', 'win' ]
138slashes.forEach(function (os) {
139  var slash = os === 'unix'
140    ? function (ss) { return ss.replace(/\\/g, '/') }
141    : function (ss) { return ss.replace(/\//g, '\\') }
142  installOk.push(os + '-file-abs-f')
143  testdirContent[os + '-file-abs-f'] = Dir({
144    'package.json': File({
145      name: os + '-file-abs-f',
146      version: '1.0.0',
147      dependencies: {
148        pkga: 'file:' + slash(testdir + '/pkga')
149      }
150    })
151  })
152  installOk.push(os + '-file-abs-fff')
153  testdirContent[os + '-file-abs-fff'] = Dir({
154    'package.json': File({
155      name: os + '-file-abs-fff',
156      version: '1.0.0',
157      dependencies: {
158        pkga: 'file://' + slash(testdir + '/pkga')
159      }
160    })
161  })
162  installOk.push(os + '-file-rel')
163  testdirContent[os + '-file-rel'] = Dir({
164    'package.json': File({
165      name: os + '-file-rel',
166      version: '1.0.0',
167      dependencies: {
168        pkga: 'file:' + slash('../pkga')
169      }
170    })
171  })
172  installOk.push(os + '-file-rel-fffff')
173  testdirContent[os + '-file-rel-fffff'] = Dir({
174    'package.json': File({
175      name: os + '-file-rel-fffff',
176      version: '1.0.0',
177      dependencies: {
178        pkga: 'file:' + slash('/////../pkga')
179      }
180    })
181  })
182})
183
184testdirContent['win-abs-drive-win'] = Dir({
185  'package.json': File({
186    name: 'win-abs-drive-win',
187    version: '1.0.0',
188    dependencies: {
189      pkga: 'file:D:\\thing\\pkga'
190    }
191  })
192})
193
194testdirContent['win-abs-drive-unix'] = Dir({
195  'package.json': File({
196    name: 'win-abs-drive-unix',
197    version: '1.0.0',
198    dependencies: {
199      pkga: 'file://D:/thing/pkga'
200    }
201  })
202})
203
204test('specifiers', function (t) {
205  t.plan(installOk.length + 2)
206  installOk.forEach(function (mod) {
207    t.test(mod, function (t) {
208      common.npm(['install', '--dry-run', '--json', 'file:' + mod], conf, function (err, code, stdout) {
209        if (err) throw err
210        t.is(code, 0, 'command ran ok')
211        t.comment(stdout.trim())
212        t.done()
213      })
214    })
215  })
216  slashes.forEach(function (os) {
217    t.test('win-abs-drive-' + os, function (t) {
218      common.npm(['install', '--dry-run', '--json', 'file:win-abs-drive-' + os], confE, function (err, code, stdout, stderr) {
219        if (err) throw err
220        t.not(code, 0, 'command errored ok')
221        t.comment(stderr.trim())
222        if (isWindows) {
223          t.test('verify failure of file-not-found or wrong drive letter on windows')
224        } else {
225          var result = JSON.parse(stdout)
226          t.is(result.error && result.error.code, 'EWINDOWSPATH', 'verify failure due to windows paths not supported on non-Windows')
227        }
228
229        t.done()
230      })
231    })
232  })
233})
234testdirContent['mkdirp'] = Dir({
235  'package.json': File({
236    name: 'mkdirp',
237    version: '9.9.9'
238  })
239})
240testdirContent['example'] = Dir({
241  'minimist': Dir({
242    'package.json': File({
243      name: 'minimist',
244      version: '9.9.9'
245    })
246  })
247})
248testdirContent['wordwrap'] = Dir({
249})
250testdirContent['prefer-pkg'] = Dir({
251  'package.json': File({
252    name: 'perfer-pkg',
253    version: '1.0.0',
254    dependencies: {
255      'mkdirp': 'latest'
256    }
257  }),
258  'latest': Dir({
259    'package.json': File({
260      name: 'mkdirp',
261      version: '9.9.9'
262    })
263  })
264})
265
266test('ambiguity', function (t) {
267  t.plan(5)
268  t.test('arg: looks like package name, is dir', function (t) {
269    common.npm(['install', 'mkdirp', '--dry-run', '--json'], conf, function (err, code, stdout) {
270      if (err) throw err
271      t.is(code, 0, 'command ran ok')
272      const result = JSON.parse(stdout.trim())
273      t.like(result, {added: [{name: 'mkdirp', version: '9.9.9'}]}, 'got local dir')
274      t.done()
275    })
276  })
277  t.test('arg: looks like package name, is package', function (t) {
278    common.npm(['install', 'wordwrap', '--dry-run', '--json'], conf, function (err, code, stdout) {
279      if (err) throw err
280      t.is(code, 0, 'command ran ok')
281      const result = JSON.parse(stdout.trim())
282      t.like(result, {added: [{name: 'wordwrap', version: '0.0.2'}]}, 'even with local dir w/o package.json, got global')
283      t.done()
284    })
285  })
286  t.test('arg: looks like github repo, is dir', function (t) {
287    common.npm(['install', 'example/minimist', '--dry-run', '--json'], conf, function (err, code, stdout) {
288      if (err) throw err
289      t.is(code, 0, 'command ran ok')
290      const result = JSON.parse(stdout.trim())
291      t.like(result, {added: [{name: 'minimist', version: '9.9.9'}]}, 'got local dir')
292      t.done()
293    })
294  })
295  t.test('package: looks like tag, has dir, use tag', function (t) {
296    common.npm(['install', '--dry-run', '--json'], {cwd: path.join(testdir, 'prefer-pkg'), env: conf.env}, function (err, code, stdout) {
297      if (err) throw err
298      t.is(code, 0, 'command ran ok')
299      const result = JSON.parse(stdout.trim())
300      t.like(result, {added: [{name: 'mkdirp', version: '0.3.5'}]}, 'got local dir')
301      t.done()
302    })
303  })
304
305  t.test('test ambiguity for github repos')
306})
307testdirContent['existing-matches'] = Dir({
308  'package.json': File({
309    name: 'existing-matches',
310    version: '1.0.0',
311    dependencies: {
312      'pkga': 'file:../pkga',
313      'pkgb': 'file:' + testdir + '/pkgb',
314      'pkgc': 'file:../pkgc',
315      'pkgd': 'file:' + testdir + '/pkgd'
316    }
317  }),
318  'node_modules': Dir({
319    'pkga': Symlink('../../pkga'),
320    'pkgd': Symlink('../../pkgd')
321  })
322})
323
324test('existing install matches', function (t) {
325  t.plan(1)
326  // have to make these by hand because tacks doesn't support absolute paths in symlinks
327  fs.symlinkSync(testdir + '/pkgb', testdir + '/existing-matches/node_modules/pkgb', 'junction')
328  fs.symlinkSync(testdir + '/pkgc', testdir + '/existing-matches/node_modules/pkgc', 'junction')
329  t.test('relative symlink counts as match of relative symlink in package.json', function (t) {
330    common.npm(['ls', '--json'], {cwd: path.join(testdir, 'existing-matches'), env: conf.env}, function (err, code, stdout) {
331      if (err) throw err
332      t.is(code, 0, 'command ran ok')
333      if (stdout) t.comment(stdout.trim())
334      t.test('specifically test that output is valid')
335      // relative symlink counts as match of relative symlink in package.json (pkga)
336      // relative symlink counts as match of abs symlink in package.json (pkgc)
337      // abs symlink counts as match of relative symlink in package.json (pkgb)
338      // abs symlink counts as match of abs symlink in package.json (pkgd)
339      t.done()
340    })
341  })
342})
343var ibdir = testdir + '/install-behavior'
344testdirContent['ib-out'] = Dir({
345  'package.json': File({
346    name: 'ib-out',
347    version: '1.0.0',
348    dependencies: {
349      'minimist': '*'
350    }
351  })
352})
353
354testdirContent['install-behavior'] = Dir({
355  'package.json': File({
356    name: 'install-behavior',
357    version: '1.0.0'
358  }),
359  'ib-in': Dir({
360    'package.json': File({
361      name: 'ib-in',
362      version: '1.0.0',
363      dependencies: {
364        'mkdirp': '*'
365      }
366    })
367  }),
368  'noext': File(Buffer.from(
369    '1f8b08000000000000032b484cce4e4c4fd52f80d07a59c5f9790c540606' +
370    '06066626260a20dadccc144c1b1841f86000923334363037343536343732' +
371    '633000728c0c80f2d4760836505a5c925804740aa5e640bca200a78708a8' +
372    '56ca4bcc4d55b252cacb4fad2851d251502a4b2d2acecccf030a19ea19e8' +
373    '1928d5720db41b47c1281805a36014501f00005012007200080000',
374    'hex'
375  )),
376  'tarball-1.0.0.tgz': File(Buffer.from(
377    '1f8b08000000000000032b484cce4e4c4fd52f80d07a59c5f9790c540606' +
378    '06066626260a20dadccc144c1b1841f8606062a6c060686c606e686a6c68' +
379    '666ec26000e480e5a9ed106ca0b4b824b108e8144acd817845014e0f1150' +
380    'ad9497989baa64a5040c85a4c49c1c251d05a5b2d4a2e2ccfc3ca0a0a19e' +
381    '819e81522dd740bb72148c8251300a4601b50100473dd15800080000',
382    'hex'
383  )),
384  'not-module': Dir({}),
385  'test-preinstall': Dir({
386    'preinstall.js': File('console.log("CWD:" + process.cwd())'),
387    'package.json': File({
388      name: 'test-preinstall',
389      version: '1.0.0',
390      scripts: {
391        'preinstall': 'node ' + ibdir + '/test-preinstall/preinstall.js'
392      }
393    })
394  })
395})
396
397test('install behavior', function (t) {
398  var ibconf = {cwd: ibdir, env: conf.env.extend({npm_config_loglevel: 'silent'}), stdio: conf.stdio}
399  t.plan(7)
400  t.test('transitive deps for in-larger-module cases', function (t) {
401    common.npm(['install', 'file:ib-in'], ibconf, function (err, code, stdout) {
402      if (err) throw err
403      t.is(code, 0, 'command ran ok')
404      if (stdout) t.comment(stdout.trim())
405      fileExists(t, ibdir + '/node_modules/mkdirp', 'transitive dep flattened')
406      isSymlink(t, ibdir + '/node_modules/ib-in', 'dep is symlink')
407      noFileExists(t, ibdir + '/ib-in/node_modules/mkdirp', 'transitive dep not nested')
408      t.done()
409    })
410  })
411  t.test('transitive deps for out-of-larger module cases', function (t) {
412    common.npm(['install', 'file:../ib-out'], ibconf, function (err, code, stdout) {
413      if (err) throw err
414      t.is(code, 0, 'command ran ok')
415      if (stdout) t.comment(stdout.trim())
416      noFileExists(t, ibdir + '/node_modules/minimist', 'transitive dep not flattened')
417      fileExists(t, testdir + '/ib-out/node_modules/minimist', 'transitive dep nested')
418      t.done()
419    })
420  })
421  t.test('transitive deps for out-of-larger module cases w/ --preserve-symlinks', function (t) {
422    rimraf.sync(ibdir + '/node_modules')
423    rimraf.sync(testdir + '/ib-out/node_modules')
424    var env = conf.env.extend({NODE_PRESERVE_SYMLINKS: '1'})
425    common.npm(['install', 'file:../ib-out'], {cwd: ibdir, env: env}, function (err, code, stdout) {
426      if (err) throw err
427      t.is(code, 0, 'command ran ok')
428      if (stdout) t.comment(stdout.trim())
429      fileExists(t, ibdir + '/node_modules/minimist', 'transitive dep flattened')
430      noFileExists(t, testdir + '/ib-out/node_modules/minimist', 'transitive dep not nested')
431      t.done()
432    })
433  })
434  t.test('tar/tgz/tar.gz should install a tarball', function (t) {
435    common.npm(['install', 'file:tarball-1.0.0.tgz'], ibconf, function (err, code, stdout) {
436      if (err) throw err
437      t.is(code, 0, 'command ran ok')
438      if (stdout) t.comment(stdout.trim())
439      fileExists(t, ibdir + '/node_modules/tarball')
440      t.done()
441    })
442  })
443  t.test('non tar/tgz/tar.gz files should give good error message', function (t) {
444    common.npm(['install', 'file:noext', '--json'], ibconf, function (err, code, stdout) {
445      if (err) throw err
446      t.not(code, 0, 'do not install files w/o extensions')
447      noFileExists(t, ibdir + '/node_modules/noext')
448      var result = JSON.parse(stdout)
449      t.like(result, {error: {code: 'ENOLOCAL'}}, 'new type of error')
450      t.done()
451    })
452  })
453  t.test('directories without package.json should give good error message', function (t) {
454    common.npm(['install', 'file:not-module', '--json'], ibconf, function (err, code, stdout) {
455      if (err) throw err
456      t.not(code, 0, 'error on dir w/o module')
457      var result = JSON.parse(stdout)
458      t.like(result, {error: {code: 'ENOLOCAL'}}, 'new type of error')
459      t.done()
460    })
461  })
462  t.test('verify preinstall step runs after finalize, such that cwd is as expected', function (t) {
463    common.npm(['install', 'file:test-preinstall'], ibconf, function (err, code, stdout) {
464      if (err) throw err
465      t.is(code, 0, 'command ran ok')
466      isSymlink(t, `${ibdir}/node_modules/test-preinstall`, 'installed as symlink')
467      t.notLike(stdout, /CWD:.*[.]staging/, 'cwd should not be in staging')
468
469      t.test('verify that env is as expected')
470      t.done()
471    })
472  })
473})
474var sbdir = testdir + '/save-behavior'
475testdirContent['save-behavior'] = Dir({
476  'package.json': File({
477    name: 'save-behavior',
478    version: '1.0.0'
479  }),
480  'npm-shrinkwrap.json': File({
481    name: 'save-behavior',
482    version: '1.0.0',
483    dependencies: {}
484  }),
485  'transitive': Dir({
486    'package.json': File({
487      name: 'transitive',
488      version: '1.0.0',
489      dependencies: {
490        'pkgc': 'file:../../pkgc'
491      }
492    })
493  })
494})
495testdirContent['sb-transitive'] = Dir({
496  'package.json': File({
497    name: 'sb-transitive',
498    version: '1.0.0',
499    dependencies: {
500      'sbta': 'file:sbta'
501    }
502  }),
503  'sbta': Dir({
504    'package.json': File({
505      name: 'sbta',
506      version: '1.0.0'
507    })
508  })
509})
510var sbdirp = testdir + '/save-behavior-pre'
511testdirContent['save-behavior-pre'] = Dir({
512  'package.json': File({
513    name: 'save-behavior',
514    version: '1.0.0'
515  }),
516  'npm-shrinkwrap.json': File({
517    name: 'save-behavior',
518    version: '1.0.0',
519    dependencies: {}
520  })
521})
522testdirContent['sb-transitive-preserve'] = Dir({
523  'package.json': File({
524    name: 'sb-transitive-preserve',
525    version: '1.0.0',
526    dependencies: {
527      'sbtb': 'file:sbtb'
528    }
529  }),
530  'sbtb': Dir({
531    'package.json': File({
532      name: 'sbtb',
533      version: '1.0.0'
534    })
535  })
536})
537test('save behavior', function (t) {
538  t.plan(6)
539  var sbconf = {cwd: sbdir, env: conf.env, stdio: conf.stdio}
540  t.test('to package.json and npm-shrinkwrap.json w/ abs', function (t) {
541    common.npm(['install', '--save', 'file:' + testdir + '/pkga'], sbconf, function (err, code, stdout) {
542      if (err) throw err
543      t.is(code, 0, 'command ran ok')
544      var pjson = readJson(sbdir + '/package.json')
545      var shrinkwrap = readJson(sbdir + '/npm-shrinkwrap.json')
546      t.is(pjson.dependencies.pkga, 'file:../pkga', 'package.json')
547      var sdep = shrinkwrap.dependencies
548      t.like(sdep, {pkga: {version: 'file:../pkga', resolved: null}}, 'npm-shrinkwrap.json')
549      t.done()
550    })
551  })
552  t.test('to package.json and npm-shrinkwrap.json w/ drive abs')
553  t.test('to package.json and npm-shrinkwrap.json w/ rel', function (t) {
554    common.npm(['install', '--save', 'file:../pkgb'], sbconf, function (err, code, stdout) {
555      if (err) throw err
556      t.is(code, 0, 'command ran ok')
557      var pjson = readJson(sbdir + '/package.json')
558      var shrinkwrap = readJson(sbdir + '/npm-shrinkwrap.json')
559      t.is(pjson.dependencies.pkgb, 'file:../pkgb', 'package.json')
560      var sdep = shrinkwrap.dependencies
561      t.like(sdep, {pkgb: {version: 'file:../pkgb', resolved: null}}, 'npm-shrinkwrap.json')
562      t.done()
563    })
564  })
565  t.test('internal transitive dependencies of shrinkwraps', function (t) {
566    common.npm(['install', '--save', 'file:transitive'], sbconf, function (err, code, stdout) {
567      if (err) throw err
568      t.is(code, 0, 'command ran ok')
569      var pjson = readJson(sbdir + '/package.json')
570      var shrinkwrap = readJson(sbdir + '/npm-shrinkwrap.json')
571      t.is(pjson.dependencies.transitive, 'file:transitive', 'package.json')
572      var sdep = shrinkwrap.dependencies.transitive || {}
573      var tdep = shrinkwrap.dependencies.pkgc
574      t.is(sdep.version, 'file:transitive', 'npm-shrinkwrap.json direct dep')
575      t.is(tdep.version, 'file:../pkgc', 'npm-shrinkwrap.json transitive dep')
576      t.done()
577    })
578  })
579  t.test('external transitive dependencies of shrinkwraps', function (t) {
580    common.npm(['install', '--save', 'file:../sb-transitive'], sbconf, function (err, code, stdout) {
581      if (err) throw err
582      t.is(code, 0, 'command ran ok')
583      var pjson = readJson(sbdir + '/package.json')
584      var shrinkwrap = readJson(sbdir + '/npm-shrinkwrap.json')
585      var deps = pjson.dependencies || {}
586      t.is(deps['sb-transitive'], 'file:../sb-transitive', 'package.json')
587      var sdep = shrinkwrap.dependencies['sb-transitive'] || {}
588      var tdep = sdep.dependencies.sbta
589      t.like(tdep, {bundled: null, version: 'file:../sb-transitive/sbta'}, 'npm-shrinkwrap.json transitive dep')
590      t.like(sdep, {bundled: null, version: 'file:../sb-transitive'}, 'npm-shrinkwrap.json direct dep')
591      t.done()
592    })
593  })
594  t.test('external transitive dependencies of shrinkwraps > preserve symlinks', function (t) {
595    var preserveEnv = conf.env.extend({NODE_PRESERVE_SYMLINKS: '1'})
596    var preserveConf = {cwd: sbdirp, env: preserveEnv, stdio: conf.stdio}
597    common.npm(['install', '--save', 'file:../sb-transitive-preserve'], preserveConf, function (err, code, stdout) {
598      if (err) throw err
599      t.is(code, 0, 'command ran ok')
600      var pjson = readJson(sbdirp + '/package.json')
601      var shrinkwrap = readJson(sbdirp + '/npm-shrinkwrap.json')
602      t.is(pjson.dependencies['sb-transitive-preserve'], 'file:../sb-transitive-preserve', 'package.json')
603      var sdep = shrinkwrap.dependencies['sb-transitive-preserve'] || {}
604      var tdep = shrinkwrap.dependencies.sbtb
605      t.like(sdep, {bundled: null, version: 'file:../sb-transitive-preserve'}, 'npm-shrinkwrap.json direct dep')
606      t.like(tdep, {bundled: null, version: 'file:../sb-transitive-preserve/sbtb'}, 'npm-shrinkwrap.json transitive dep')
607      t.done()
608    })
609  })
610})
611
612var rmdir = testdir + '/remove-behavior'
613testdirContent['remove-behavior'] = Dir({
614  'rmsymlink': Dir({
615    'package.json': File({
616      name: 'remove-behavior',
617      version: '1.0.0',
618      dependencies: {
619        dep1: 'file:dep1'
620      }
621    }),
622    'package-lock.json': File({
623      name: 'remove-behavior',
624      version: '1.0.0',
625      lockfileVersion: 1,
626      requires: true,
627      dependencies: {
628        dep1: {
629          version: 'file:dep1',
630          requires: {
631            dep2: 'file:dep2'
632          },
633          dependencies: {
634            dep2: {
635              version: 'file:dep2',
636              bundled: true
637            }
638          }
639        }
640      }
641    }),
642    dep1: Dir({
643      'package.json': File({
644        name: 'dep1',
645        version: '1.0.0',
646        dependencies: {
647          dep2: 'file:../dep2'
648        }
649      }),
650      'node_modules': Dir({
651        dep2: Symlink('../../dep2')
652      })
653    }),
654    dep2: Dir({
655      'package.json': File({
656        name: 'dep2',
657        version: '1.0.0'
658      })
659    }),
660    'node_modules': Dir({
661      dep1: Symlink('../dep1')
662    })
663  }),
664  'rmesymlink': Dir({
665    'package.json': File({
666      name: 'remove-behavior',
667      version: '1.0.0',
668      dependencies: {
669        edep1: 'file:../edep1'
670      }
671    }),
672    'package-lock.json': File({
673      name: 'remove-behavior',
674      version: '1.0.0',
675      lockfileVersion: 1,
676      requires: true,
677      dependencies: {
678        edep1: {
679          version: 'file:../edep1',
680          requires: {
681            edep2: 'file:../edep2'
682          },
683          dependencies: {
684            edep2: {
685              version: 'file:../edep2',
686              bundled: true
687            }
688          }
689        }
690      }
691    }),
692    'node_modules': Dir({
693      edep1: Symlink('../../edep1')
694    })
695  }),
696  edep1: Dir({
697    'package.json': File({
698      name: 'edep1',
699      version: '1.0.0',
700      dependencies: {
701        edep2: 'file:../edep2'
702      }
703    }),
704    'node_modules': Dir({
705      edep2: Symlink('../../edep2')
706    })
707  }),
708  edep2: Dir({
709    'package.json': File({
710      name: 'edep2',
711      version: '1.0.0'
712    })
713  })
714})
715
716test('removal', function (t) {
717  t.plan(2)
718
719  t.test('should remove the symlink', (t) => {
720    const rmconf = {cwd: `${rmdir}/rmsymlink`, env: conf.env, stdio: conf.stdio}
721    return common.npm(['uninstall', 'dep1'], rmconf).spread((code, stdout) => {
722      t.is(code, 0, 'uninstall ran ok')
723      t.comment(stdout)
724      noFileExists(t, `${rmdir}/rmsymlink/node_modules/dep1`, 'removed symlink')
725      noFileExists(t, `${rmdir}/rmsymlink/dep1/node_modules/dep2`, 'removed transitive dep')
726      fileExists(t, `${rmdir}/rmsymlink/dep2`, 'original transitive dep still exists')
727    })
728  })
729  t.test("should not remove transitive deps if it's outside the package and --preserver-symlinks isn't set", (t) => {
730    const rmconf = {cwd: `${rmdir}/rmesymlink`, env: conf.env, stdio: conf.stdio}
731    return common.npm(['uninstall', 'edep1'], rmconf).spread((code, stdout) => {
732      t.is(code, 0, 'uninstall ran ok')
733      t.comment(stdout)
734      noFileExists(t, `${rmdir}/rmsymlink/node_modules/edep1`, 'removed symlink')
735      fileExists(t, `${rmdir}/edep1/node_modules/edep2`, 'did NOT remove transitive dep')
736      fileExists(t, `${rmdir}/edep2`, 'original transitive dep still exists')
737    })
738  })
739})
740
741test('misc', function (t) {
742  t.plan(3)
743  t.test('listing: should look right, not include version')
744  t.test('outdated: show LOCAL for wanted / latest')
745  t.test('update: if specifier exists, do nothing. otherwise as if `npm install`.')
746})
747
748test('cleanup', function (t) {
749  server.close()
750  cleanup()
751  t.done()
752})
753