You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

630 lines
20 KiB

'use strict'
var path = require('path')
var test = require('tap').test
var fs = require('fs')
var rimraf = require('rimraf')
var mr = require('npm-registry-mock')
var Tacks = require('tacks')
var File = Tacks.File
var Symlink = Tacks.Symlink
var Dir = Tacks.Dir
var common = require('../common-tap.js')
var isWindows = require('../../lib/utils/is-windows.js')
var basedir = path.join(__dirname, path.basename(__filename, '.js'))
var testdir = path.join(basedir, 'testdir')
var cachedir = path.join(basedir, 'cache')
var globaldir = path.join(basedir, 'global')
var tmpdir = path.join(basedir, 'tmp')
var conf = {
cwd: testdir,
env: common.emptyEnv().extend({
npm_config_cache: cachedir,
npm_config_tmp: tmpdir,
npm_config_prefix: globaldir,
npm_config_registry: common.registry,
npm_config_loglevel: 'error'
}),
stdio: [0, 'pipe', 2]
}
var confE = {
cwd: testdir,
env: conf.env,
stdio: [0, 'pipe', 'pipe']
}
function readJson (file) {
return JSON.parse(fs.readFileSync(file))
}
function isSymlink (t, file, message) {
try {
var info = fs.lstatSync(file)
t.is(info.isSymbolicLink(), true, message)
} catch (ex) {
if (ex.code === 'ENOENT') {
t.fail(message, {found: null, wanted: 'symlink', compare: 'fs.lstat(' + file + ')'})
} else {
t.fail(message, {found: ex, wanted: 'symlink', compare: 'fs.lstat(' + file + ')'})
}
}
}
function fileExists (t, file, message) {
try {
fs.statSync(file)
t.pass(message)
} catch (ex) {
if (ex.code === 'ENOENT') {
t.fail(message, {found: null, wanted: 'exists', compare: 'fs.stat(' + file + ')'})
} else {
t.fail(message, {found: ex, wanted: 'exists', compare: 'fs.stat(' + file + ')'})
}
}
}
function noFileExists (t, file, message) {
try {
fs.statSync(file)
t.fail(message, {found: 'exists', wanted: 'not exists', compare: 'fs.stat(' + file + ')'})
} catch (ex) {
if (ex.code === 'ENOENT') {
t.pass(message)
} else {
t.fail(message, {found: ex, wanted: 'not exists', compare: 'fs.stat(' + file + ')'})
}
}
}
var server
var testdirContent = {
node_modules: Dir({}),
pkga: Dir({
'package.json': File({
name: 'pkga',
version: '1.0.0'
})
}),
pkgb: Dir({
'package.json': File({
name: 'pkgb',
version: '1.0.0'
})
}),
pkgc: Dir({
'package.json': File({
name: 'pkgc',
version: '1.0.0'
})
}),
pkgd: Dir({
'package.json': File({
name: 'pkgd',
version: '1.0.0'
})
})
}
var fixture
function setup () {
fixture = new Tacks(Dir({
cache: Dir(),
global: Dir(),
tmp: Dir(),
testdir: Dir(testdirContent)
}))
cleanup()
fixture.create(basedir)
}
function cleanup () {
fixture.remove(basedir)
}
test('setup', function (t) {
process.nextTick(function () {
setup()
mr({port: common.port, throwOnUnmatched: true}, function (err, s) {
if (err) throw err
server = s
t.done()
})
})
})
var installOk = []
var slashes = [ 'unix', 'win' ]
slashes.forEach(function (os) {
var slash = os === 'unix'
? function (ss) { return ss.replace(/\\/g, '/') }
: function (ss) { return ss.replace(/\//g, '\\') }
installOk.push(os + '-file-abs-f')
testdirContent[os + '-file-abs-f'] = Dir({
'package.json': File({
name: os + '-file-abs-f',
version: '1.0.0',
dependencies: {
pkga: 'file:' + slash(testdir + '/pkga')
}
})
})
installOk.push(os + '-file-abs-fff')
testdirContent[os + '-file-abs-fff'] = Dir({
'package.json': File({
name: os + '-file-abs-fff',
version: '1.0.0',
dependencies: {
pkga: 'file://' + slash(testdir + '/pkga')
}
})
})
installOk.push(os + '-file-rel')
testdirContent[os + '-file-rel'] = Dir({
'package.json': File({
name: os + '-file-rel',
version: '1.0.0',
dependencies: {
pkga: 'file:' + slash('../pkga')
}
})
})
installOk.push(os + '-file-rel-fffff')
testdirContent[os + '-file-rel-fffff'] = Dir({
'package.json': File({
name: os + '-file-rel-fffff',
version: '1.0.0',
dependencies: {
pkga: 'file:' + slash('/////../pkga')
}
})
})
})
testdirContent['win-abs-drive-win'] = Dir({
'package.json': File({
name: 'win-abs-drive-win',
version: '1.0.0',
dependencies: {
pkga: 'file:D:\\thing\\pkga'
}
})
})
testdirContent['win-abs-drive-unix'] = Dir({
'package.json': File({
name: 'win-abs-drive-unix',
version: '1.0.0',
dependencies: {
pkga: 'file://D:/thing/pkga'
}
})
})
test('specifiers', function (t) {
t.plan(installOk.length + 2)
installOk.forEach(function (mod) {
t.test(mod, function (t) {
common.npm(['install', '--dry-run', '--json', 'file:' + mod], conf, function (err, code, stdout) {
if (err) throw err
t.is(code, 0, 'command ran ok')
t.comment(stdout.trim())
t.done()
})
})
})
slashes.forEach(function (os) {
t.test('win-abs-drive-' + os, function (t) {
common.npm(['install', '--dry-run', '--json', 'file:win-abs-drive-' + os], confE, function (err, code, stdout, stderr) {
if (err) throw err
t.not(code, 0, 'command errored ok')
t.comment(stderr.trim())
if (isWindows) {
t.test('verify failure of file-not-found or wrong drive letter on windows')
} else {
var result = JSON.parse(stdout)
t.is(result.error && result.error.code, 'EWINDOWSPATH', 'verify failure due to windows paths not supported on non-Windows')
}
t.done()
})
})
})
})
testdirContent['mkdirp'] = Dir({
'package.json': File({
name: 'mkdirp',
version: '9.9.9'
})
})
testdirContent['example'] = Dir({
'minimist': Dir({
'package.json': File({
name: 'minimist',
version: '9.9.9'
})
})
})
testdirContent['wordwrap'] = Dir({
})
testdirContent['prefer-pkg'] = Dir({
'package.json': File({
name: 'perfer-pkg',
version: '1.0.0',
dependencies: {
'mkdirp': 'latest'
}
}),
'latest': Dir({
'package.json': File({
name: 'mkdirp',
version: '9.9.9'
})
})
})
test('ambiguity', function (t) {
t.plan(5)
t.test('arg: looks like package name, is dir', function (t) {
common.npm(['install', 'mkdirp', '--dry-run', '--json'], conf, function (err, code, stdout) {
if (err) throw err
t.is(code, 0, 'command ran ok')
const result = JSON.parse(stdout.trim())
t.like(result, {added: [{name: 'mkdirp', version: '9.9.9'}]}, 'got local dir')
t.done()
})
})
t.test('arg: looks like package name, is package', function (t) {
common.npm(['install', 'wordwrap', '--dry-run', '--json'], conf, function (err, code, stdout) {
if (err) throw err
t.is(code, 0, 'command ran ok')
const result = JSON.parse(stdout.trim())
t.like(result, {added: [{name: 'wordwrap', version: '0.0.2'}]}, 'even with local dir w/o package.json, got global')
t.done()
})
})
t.test('arg: looks like github repo, is dir', function (t) {
common.npm(['install', 'example/minimist', '--dry-run', '--json'], conf, function (err, code, stdout) {
if (err) throw err
t.is(code, 0, 'command ran ok')
const result = JSON.parse(stdout.trim())
t.like(result, {added: [{name: 'minimist', version: '9.9.9'}]}, 'got local dir')
t.done()
})
})
t.test('package: looks like tag, has dir, use tag', function (t) {
common.npm(['install', '--dry-run', '--json'], {cwd: path.join(testdir, 'prefer-pkg'), env: conf.env}, function (err, code, stdout) {
if (err) throw err
t.is(code, 0, 'command ran ok')
const result = JSON.parse(stdout.trim())
t.like(result, {added: [{name: 'mkdirp', version: '0.3.5'}]}, 'got local dir')
t.done()
})
})
t.test('test ambiguity for github repos')
})
testdirContent['existing-matches'] = Dir({
'package.json': File({
name: 'existing-matches',
version: '1.0.0',
dependencies: {
'pkga': 'file:../pkga',
'pkgb': 'file:' + testdir + '/pkgb',
'pkgc': 'file:../pkgc',
'pkgd': 'file:' + testdir + '/pkgd'
}
}),
'node_modules': Dir({
'pkga': Symlink('../../pkga'),
'pkgd': Symlink('../../pkgd')
})
})
test('existing install matches', function (t) {
t.plan(1)
// have to make these by hand because tacks doesn't support absolute paths in symlinks
fs.symlinkSync(testdir + '/pkgb', testdir + '/existing-matches/node_modules/pkgb', 'junction')
fs.symlinkSync(testdir + '/pkgc', testdir + '/existing-matches/node_modules/pkgc', 'junction')
t.test('relative symlink counts as match of relative symlink in package.json', function (t) {
common.npm(['ls', '--json'], {cwd: path.join(testdir, 'existing-matches'), env: conf.env}, function (err, code, stdout) {
if (err) throw err
t.is(code, 0, 'command ran ok')
if (stdout) t.comment(stdout.trim())
t.test('specifically test that output is valid')
// relative symlink counts as match of relative symlink in package.json (pkga)
// relative symlink counts as match of abs symlink in package.json (pkgc)
// abs symlink counts as match of relative symlink in package.json (pkgb)
// abs symlink counts as match of abs symlink in package.json (pkgd)
t.done()
})
})
})
var ibdir = testdir + '/install-behavior'
testdirContent['ib-out'] = Dir({
'package.json': File({
name: 'ib-out',
version: '1.0.0',
dependencies: {
'minimist': '*'
}
})
})
testdirContent['install-behavior'] = Dir({
'package.json': File({
name: 'install-behavior',
version: '1.0.0'
}),
'ib-in': Dir({
'package.json': File({
name: 'ib-in',
version: '1.0.0',
dependencies: {
'mkdirp': '*'
}
})
}),
'noext': File(new Buffer(
'1f8b08000000000000032b484cce4e4c4fd52f80d07a59c5f9790c540606' +
'06066626260a20dadccc144c1b1841f86000923334363037343536343732' +
'633000728c0c80f2d4760836505a5c925804740aa5e640bca200a78708a8' +
'56ca4bcc4d55b252cacb4fad2851d251502a4b2d2acecccf030a19ea19e8' +
'1928d5720db41b47c1281805a36014501f00005012007200080000',
'hex'
)),
'tarball-1.0.0.tgz': File(new Buffer(
'1f8b08000000000000032b484cce4e4c4fd52f80d07a59c5f9790c540606' +
'06066626260a20dadccc144c1b1841f8606062a6c060686c606e686a6c68' +
'666ec26000e480e5a9ed106ca0b4b824b108e8144acd817845014e0f1150' +
'ad9497989baa64a5040c85a4c49c1c251d05a5b2d4a2e2ccfc3ca0a0a19e' +
'819e81522dd740bb72148c8251300a4601b50100473dd15800080000',
'hex'
)),
'not-module': Dir({}),
'test-preinstall': Dir({
'preinstall.js': File('console.log("CWD:" + process.cwd())'),
'package.json': File({
name: 'test-preinstall',
version: '1.0.0',
scripts: {
'preinstall': 'node ' + ibdir + '/test-preinstall/preinstall.js'
}
})
})
})
test('install behavior', function (t) {
var ibconf = {cwd: ibdir, env: conf.env.extend({npm_config_loglevel: 'silent'}), stdio: conf.stdio}
t.plan(7)
t.test('transitive deps for in-larger-module cases', function (t) {
common.npm(['install', 'file:ib-in'], ibconf, function (err, code, stdout) {
if (err) throw err
t.is(code, 0, 'command ran ok')
if (stdout) t.comment(stdout.trim())
fileExists(t, ibdir + '/node_modules/mkdirp', 'transitive dep flattened')
isSymlink(t, ibdir + '/node_modules/ib-in', 'dep is symlink')
noFileExists(t, ibdir + '/ib-in/node_modules/mkdirp', 'transitive dep not nested')
t.done()
})
})
t.test('transitive deps for out-of-larger module cases', function (t) {
common.npm(['install', 'file:../ib-out'], ibconf, function (err, code, stdout) {
if (err) throw err
t.is(code, 0, 'command ran ok')
if (stdout) t.comment(stdout.trim())
noFileExists(t, ibdir + '/node_modules/minimist', 'transitive dep not flattened')
fileExists(t, testdir + '/ib-out/node_modules/minimist', 'transitive dep nested')
t.done()
})
})
t.test('transitive deps for out-of-larger module cases w/ --preserve-symlinks', function (t) {
rimraf.sync(ibdir + '/node_modules')
rimraf.sync(testdir + '/ib-out/node_modules')
var env = conf.env.extend({NODE_PRESERVE_SYMLINKS: '1'})
common.npm(['install', 'file:../ib-out'], {cwd: ibdir, env: env}, function (err, code, stdout) {
if (err) throw err
t.is(code, 0, 'command ran ok')
if (stdout) t.comment(stdout.trim())
fileExists(t, ibdir + '/node_modules/minimist', 'transitive dep flattened')
noFileExists(t, testdir + '/ib-out/node_modules/minimist', 'transitive dep not nested')
t.done()
})
})
t.test('tar/tgz/tar.gz should install a tarball', function (t) {
common.npm(['install', 'file:tarball-1.0.0.tgz'], ibconf, function (err, code, stdout) {
if (err) throw err
t.is(code, 0, 'command ran ok')
if (stdout) t.comment(stdout.trim())
fileExists(t, ibdir + '/node_modules/tarball')
t.done()
})
})
t.test('non tar/tgz/tar.gz files should give good error message', function (t) {
common.npm(['install', 'file:noext', '--json'], ibconf, function (err, code, stdout) {
if (err) throw err
t.not(code, 0, 'do not install files w/o extensions')
noFileExists(t, ibdir + '/node_modules/noext')
var result = JSON.parse(stdout)
t.like(result, {error: {code: 'ENOLOCAL'}}, 'new type of error')
t.done()
})
})
t.test('directories without package.json should give good error message', function (t) {
common.npm(['install', 'file:not-module', '--json'], ibconf, function (err, code, stdout) {
if (err) throw err
t.not(code, 0, 'error on dir w/o module')
var result = JSON.parse(stdout)
t.like(result, {error: {code: 'ENOLOCAL'}}, 'new type of error')
t.done()
})
})
t.test('verify preinstall step runs after finalize, such that cwd is as expected', function (t) {
common.npm(['install', 'file:test-preinstall'], ibconf, function (err, code, stdout) {
if (err) throw err
t.is(code, 0, 'command ran ok')
isSymlink(t, `${ibdir}/node_modules/test-preinstall`, 'installed as symlink')
t.notLike(stdout, /CWD:.*[.]staging/, 'cwd should not be in staging')
t.test('verify that env is as expected')
t.done()
})
})
})
var sbdir = testdir + '/save-behavior'
testdirContent['save-behavior'] = Dir({
'package.json': File({
name: 'save-behavior',
version: '1.0.0'
}),
'npm-shrinkwrap.json': File({
name: 'save-behavior',
version: '1.0.0',
dependencies: {}
}),
'transitive': Dir({
'package.json': File({
name: 'transitive',
version: '1.0.0',
dependencies: {
'pkgc': 'file:../../pkgc'
}
})
})
})
testdirContent['sb-transitive'] = Dir({
'package.json': File({
name: 'sb-transitive',
version: '1.0.0',
dependencies: {
'sbta': 'file:sbta'
}
}),
'sbta': Dir({
'package.json': File({
name: 'sbta',
version: '1.0.0'
})
})
})
var sbdirp = testdir + '/save-behavior-pre'
testdirContent['save-behavior-pre'] = Dir({
'package.json': File({
name: 'save-behavior',
version: '1.0.0'
}),
'npm-shrinkwrap.json': File({
name: 'save-behavior',
version: '1.0.0',
dependencies: {}
})
})
testdirContent['sb-transitive-preserve'] = Dir({
'package.json': File({
name: 'sb-transitive-preserve',
version: '1.0.0',
dependencies: {
'sbtb': 'file:sbtb'
}
}),
'sbtb': Dir({
'package.json': File({
name: 'sbtb',
version: '1.0.0'
})
})
})
test('save behavior', function (t) {
t.plan(6)
var sbconf = {cwd: sbdir, env: conf.env, stdio: conf.stdio}
t.test('to package.json and npm-shrinkwrap.json w/ abs', function (t) {
common.npm(['install', '--save', 'file:' + testdir + '/pkga'], sbconf, function (err, code, stdout) {
if (err) throw err
t.is(code, 0, 'command ran ok')
var pjson = readJson(sbdir + '/package.json')
var shrinkwrap = readJson(sbdir + '/npm-shrinkwrap.json')
t.is(pjson.dependencies.pkga, 'file:../pkga', 'package.json')
var sdep = shrinkwrap.dependencies
t.like(sdep, {pkga: {version: 'file:../pkga', resolved: null}}, 'npm-shrinkwrap.json')
t.done()
})
})
t.test('to package.json and npm-shrinkwrap.json w/ drive abs')
t.test('to package.json and npm-shrinkwrap.json w/ rel', function (t) {
common.npm(['install', '--save', 'file:../pkgb'], sbconf, function (err, code, stdout) {
if (err) throw err
t.is(code, 0, 'command ran ok')
var pjson = readJson(sbdir + '/package.json')
var shrinkwrap = readJson(sbdir + '/npm-shrinkwrap.json')
t.is(pjson.dependencies.pkgb, 'file:../pkgb', 'package.json')
var sdep = shrinkwrap.dependencies
t.like(sdep, {pkgb: {version: 'file:../pkgb', resolved: null}}, 'npm-shrinkwrap.json')
t.done()
})
})
t.test('internal transitive dependencies of shrinkwraps', function (t) {
common.npm(['install', '--save', 'file:transitive'], sbconf, function (err, code, stdout) {
if (err) throw err
t.is(code, 0, 'command ran ok')
var pjson = readJson(sbdir + '/package.json')
var shrinkwrap = readJson(sbdir + '/npm-shrinkwrap.json')
t.is(pjson.dependencies.transitive, 'file:transitive', 'package.json')
var sdep = shrinkwrap.dependencies.transitive || {}
var tdep = shrinkwrap.dependencies.pkgc
t.is(sdep.version, 'file:transitive', 'npm-shrinkwrap.json direct dep')
t.is(tdep.version, 'file:../pkgc', 'npm-shrinkwrap.json transitive dep')
t.done()
})
})
t.test('external transitive dependencies of shrinkwraps', function (t) {
common.npm(['install', '--save', 'file:../sb-transitive'], sbconf, function (err, code, stdout) {
if (err) throw err
t.is(code, 0, 'command ran ok')
var pjson = readJson(sbdir + '/package.json')
var shrinkwrap = readJson(sbdir + '/npm-shrinkwrap.json')
var deps = pjson.dependencies || {}
t.is(deps['sb-transitive'], 'file:../sb-transitive', 'package.json')
var sdep = shrinkwrap.dependencies['sb-transitive'] || {}
var tdep = sdep.dependencies.sbta
t.like(tdep, {bundled: true, version: 'file:../sb-transitive/sbta'}, 'npm-shrinkwrap.json transitive dep')
t.like(sdep, {bundled: null, version: 'file:../sb-transitive'}, 'npm-shrinkwrap.json direct dep')
t.done()
})
})
t.test('external transitive dependencies of shrinkwraps > preserve symlinks', function (t) {
var preserveEnv = conf.env.extend({NODE_PRESERVE_SYMLINKS: '1'})
var preserveConf = {cwd: sbdirp, env: preserveEnv, stdio: conf.stdio}
common.npm(['install', '--save', 'file:../sb-transitive-preserve'], preserveConf, function (err, code, stdout) {
if (err) throw err
t.is(code, 0, 'command ran ok')
var pjson = readJson(sbdirp + '/package.json')
var shrinkwrap = readJson(sbdirp + '/npm-shrinkwrap.json')
t.is(pjson.dependencies['sb-transitive-preserve'], 'file:../sb-transitive-preserve', 'package.json')
var sdep = shrinkwrap.dependencies['sb-transitive-preserve'] || {}
var tdep = shrinkwrap.dependencies.sbtb
t.like(sdep, {bundled: null, version: 'file:../sb-transitive-preserve'}, 'npm-shrinkwrap.json direct dep')
t.like(tdep, {bundled: null, version: 'file:../sb-transitive-preserve/sbtb'}, 'npm-shrinkwrap.json transitive dep')
t.done()
})
})
})
test('removal', function (t) {
t.plan(3)
t.test('should remove the symlink')
t.test('should not remove the transitive deps if it was not a `link:` type specifier.')
t.test("should not remove transitive deps if it's outside the package and --preserver-symlinks isn't set")
})
test('misc', function (t) {
t.plan(3)
t.test('listing: should look right, not include version')
t.test('outdated: show LOCAL for wanted / latest')
t.test('update: if specifier exists, do nothing. otherwise as if `npm install`.')
})
test('cleanup', function (t) {
server.close()
cleanup()
t.done()
})