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