1'use strict' 2 3const BB = require('bluebird') 4 5const common = BB.promisifyAll(require('../common-tap.js')) 6const fs = BB.promisifyAll(require('fs')) 7const mr = BB.promisify(require('npm-registry-mock')) 8const path = require('path') 9const rimraf = BB.promisify(require('rimraf')) 10const Tacks = require('tacks') 11const test = require('tap').test 12 13const Dir = Tacks.Dir 14const File = Tacks.File 15const cacheDir = common.cache 16const testDir = common.pkg 17 18const EXEC_OPTS = { 19 cwd: testDir, 20 nodeExecPath: process.execPath 21} 22 23const PKG = { 24 name: 'top', 25 version: '1.2.3', 26 scripts: { 27 install: 'node -p process.env.npm_config_foo' 28 }, 29 dependencies: { 30 optimist: '0.6.0', 31 clean: '2.1.6' 32 } 33} 34let RAW_LOCKFILE 35let SERVER 36let TREE 37 38function scrubFrom (tree) { 39 // npm ci and npm i write different `from` fields for dependency deps. This 40 // is fine any ok, but it messes with `t.deepEqual` comparisons. 41 function _scrubFrom (deps) { 42 Object.keys(deps).forEach((k) => { 43 deps[k].from = '' 44 if (deps[k].dependencies) { _scrubFrom(deps[k].dependencies) } 45 }) 46 } 47 tree.dependencies && _scrubFrom(tree.dependencies) 48} 49 50test('setup', () => { 51 const fixture = new Tacks(Dir({ 52 'package.json': File(PKG) 53 })) 54 return Promise.all([rimraf(cacheDir), rimraf(testDir)]).then(() => { 55 fixture.create(testDir) 56 return mr({port: common.port}) 57 }) 58 .then((server) => { 59 SERVER = server 60 return common.npm([ 61 'install', 62 '--registry', common.registry 63 ], EXEC_OPTS) 64 }) 65 .then(() => fs.readFileAsync( 66 path.join(testDir, 'package-lock.json'), 67 'utf8') 68 ) 69 .then((lock) => { 70 RAW_LOCKFILE = lock 71 }) 72 .then(() => common.npm(['ls', '--json'], EXEC_OPTS)) 73 .then((ret) => { 74 TREE = scrubFrom(JSON.parse(ret[1])) 75 }) 76}) 77 78test('basic installation', (t) => { 79 const fixture = new Tacks(Dir({ 80 'package.json': File(PKG), 81 'package-lock.json': File(RAW_LOCKFILE) 82 })) 83 return rimraf(testDir) 84 .then(() => fixture.create(testDir)) 85 .then(() => common.npm([ 86 'ci', 87 '--foo=asdf', 88 '--registry', common.registry, 89 '--loglevel', 'warn' 90 ], EXEC_OPTS)) 91 .then((ret) => { 92 const code = ret[0] 93 const stdout = ret[1] 94 const stderr = ret[2] 95 t.equal(code, 0, 'command completed without error') 96 t.equal(stderr.trim(), '', 'no output on stderr') 97 t.match( 98 stdout.trim(), 99 /\nasdf\nadded 6 packages in \d+(?:\.\d+)?s$/, 100 'no warnings on stderr, and final output has right number of packages' 101 ) 102 return fs.readdirAsync(path.join(testDir, 'node_modules')) 103 }) 104 .then((modules) => { 105 t.deepEqual(modules.sort(), [ 106 'async', 'checker', 'clean', 'minimist', 'optimist', 'wordwrap' 107 ], 'packages installed') 108 return BB.all(modules.map((mod) => { 109 return fs.readFileAsync( 110 path.join(testDir, 'node_modules', mod, 'package.json') 111 ) 112 .then((f) => JSON.parse(f)) 113 .then((pkgjson) => { 114 t.equal(pkgjson.name, mod, `${mod} package name correct`) 115 t.match( 116 pkgjson._integrity, 117 /sha\d+-[a-z0-9=+/]+$/i, 118 `${mod} pkgjson has _integrity` 119 ) 120 t.match( 121 pkgjson._resolved, 122 new RegExp(`http.*/-/${mod}-${pkgjson.version}.tgz`), 123 `${mod} pkgjson has correct _resolved` 124 ) 125 t.match( 126 pkgjson._from, 127 new RegExp(`${mod}@.*`), 128 `${mod} pkgjson has _from field` 129 ) 130 }) 131 })) 132 }) 133 .then(() => fs.readFileAsync( 134 path.join(testDir, 'package-lock.json'), 135 'utf8') 136 ) 137 .then((lock) => t.equal(lock, RAW_LOCKFILE, 'package-lock.json unchanged')) 138 .then(() => common.npm(['ls', '--json'], EXEC_OPTS)) 139 .then((ret) => { 140 const lsResult = JSON.parse(ret[1]) 141 t.equal(ret[0], 0, 'ls exited successfully') 142 t.deepEqual(scrubFrom(lsResult), TREE, 'tree matches one from `install`') 143 }) 144}) 145 146test('supports npm-shrinkwrap.json as well', (t) => { 147 const fixture = new Tacks(Dir({ 148 'package.json': File(PKG), 149 'npm-shrinkwrap.json': File(RAW_LOCKFILE) 150 })) 151 return rimraf(testDir) 152 .then(() => fixture.create(testDir)) 153 .then(() => common.npm([ 154 'ci', 155 '--foo=asdf', 156 '--registry', common.registry, 157 '--loglevel', 'warn' 158 ], EXEC_OPTS)) 159 .then((ret) => { 160 const code = ret[0] 161 const stdout = ret[1] 162 const stderr = ret[2] 163 t.equal(code, 0, 'command completed without error') 164 t.equal(stderr.trim(), '', 'no output on stderr') 165 t.match( 166 stdout.trim(), 167 /\nasdf\nadded 6 packages in \d+(?:\.\d+)?s$/, 168 'no warnings on stderr, and final output has right number of packages' 169 ) 170 }) 171 .then(() => common.npm(['ls', '--json'], EXEC_OPTS)) 172 .then((ret) => { 173 t.equal(ret[0], 0, 'ls exited successfully') 174 t.deepEqual( 175 scrubFrom(JSON.parse(ret[1])), 176 TREE, 177 'tree matches one from `install`' 178 ) 179 }) 180 .then(() => fs.readFileAsync( 181 path.join(testDir, 'npm-shrinkwrap.json'), 182 'utf8') 183 ) 184 .then((lock) => t.equal(lock, RAW_LOCKFILE, 'npm-shrinkwrap.json unchanged')) 185 .then(() => fs.readdirAsync(path.join(testDir))) 186 .then((files) => t.notOk( 187 files.some((f) => f === 'package-lock.json'), 188 'no package-lock.json created' 189 )) 190}) 191 192test('removes existing node_modules/ before installing', (t) => { 193 const fixture = new Tacks(Dir({ 194 'package.json': File(PKG), 195 'package-lock.json': File(RAW_LOCKFILE), 196 'node_modules': Dir({ 197 foo: Dir({ 198 'index.js': File('"hello world"') 199 }) 200 }) 201 })) 202 return rimraf(testDir) 203 .then(() => fixture.create(testDir)) 204 .then(() => common.npm([ 205 'ci', 206 '--foo=asdf', 207 '--registry', common.registry, 208 '--loglevel', 'warn' 209 ], EXEC_OPTS)) 210 .then((ret) => { 211 const code = ret[0] 212 const stderr = ret[2] 213 t.equal(code, 0, 'command completed without error') 214 t.match( 215 stderr.trim(), 216 /^npm.*WARN.*removing existing node_modules/, 217 'user warned that existing node_modules were removed' 218 ) 219 return fs.readdirAsync(path.join(testDir, 'node_modules')) 220 }) 221 .then((modules) => { 222 t.deepEqual(modules.sort(), [ 223 'async', 'checker', 'clean', 'minimist', 'optimist', 'wordwrap' 224 ], 'packages installed, with old node_modules dir gone') 225 }) 226 .then(() => common.npm(['ls'], EXEC_OPTS)) 227 .then((ret) => t.equal(ret[0], 0, 'ls exited successfully')) 228 .then(() => fs.readFileAsync( 229 path.join(testDir, 'package-lock.json'), 230 'utf8') 231 ) 232 .then((lock) => t.equal(lock, RAW_LOCKFILE, 'package-lock.json unchanged')) 233}) 234 235test('installs all package types correctly') 236 237test('errors if package-lock.json missing', (t) => { 238 const fixture = new Tacks(Dir({ 239 'package.json': File(PKG) 240 })) 241 return rimraf(testDir) 242 .then(() => fixture.create(testDir)) 243 .then(() => common.npm([ 244 'ci', 245 '--foo=asdf', 246 '--registry', common.registry, 247 '--loglevel', 'warn' 248 ], EXEC_OPTS)) 249 .then((ret) => { 250 const code = ret[0] 251 const stdout = ret[1] 252 const stderr = ret[2] 253 t.equal(code, 1, 'command errored') 254 t.equal(stdout.trim(), '', 'no output on stdout') 255 t.match( 256 stderr.trim(), 257 /can only install packages with an existing package-lock/i, 258 'user informed about the issue' 259 ) 260 return fs.readdirAsync(path.join(testDir)) 261 }) 262 .then((dir) => { 263 t.notOk(dir.some((f) => f === 'node_modules'), 'no node_modules installed') 264 t.notOk( 265 dir.some((f) => f === 'package-lock.json'), 266 'no package-lock.json created' 267 ) 268 }) 269}) 270 271test('errors if package-lock.json invalid', (t) => { 272 const badJson = JSON.parse(RAW_LOCKFILE) 273 delete badJson.dependencies.optimist 274 const fixture = new Tacks(Dir({ 275 'package.json': File(PKG), 276 'package-lock.json': File(badJson) 277 })) 278 return rimraf(testDir) 279 .then(() => fixture.create(testDir)) 280 .then(() => common.npm([ 281 'ci', 282 '--foo=asdf', 283 '--registry', common.registry, 284 '--loglevel', 'warn' 285 ], EXEC_OPTS)) 286 .then((ret) => { 287 const code = ret[0] 288 const stdout = ret[1] 289 const stderr = ret[2] 290 t.equal(code, 1, 'command errored') 291 t.equal(stdout.trim(), '', 'no output on stdout') 292 t.match( 293 stderr.trim(), 294 /can only install packages when your package.json/i, 295 'user informed about the issue' 296 ) 297 return fs.readdirAsync(path.join(testDir)) 298 }) 299 .then((dir) => { 300 t.notOk(dir.some((f) => f === 'node_modules'), 'no node_modules installed') 301 }) 302 .then(() => fs.readFileAsync( 303 path.join(testDir, 'package-lock.json'), 304 'utf8') 305 ) 306 .then((lock) => t.deepEqual( 307 JSON.parse(lock), 308 badJson, 309 'bad package-lock.json left unchanged') 310 ) 311}) 312 313test('correct cache location when using cache config', (t) => { 314 const fixture = new Tacks(Dir({ 315 'package.json': File(PKG), 316 'package-lock.json': File(RAW_LOCKFILE) 317 })) 318 return Promise.all([rimraf(cacheDir), rimraf(testDir)]) 319 .then(() => fixture.create(cacheDir)) 320 .then(() => fixture.create(testDir)) 321 .then(() => common.npm([ 322 'ci', 323 `--cache=${cacheDir}`, 324 '--foo=asdf', 325 '--registry', common.registry, 326 '--loglevel', 'warn' 327 ], EXEC_OPTS)) 328 .then((ret) => { 329 const code = ret[0] 330 const stderr = ret[2] 331 t.equal(code, 0, 'command completed without error') 332 t.equal(stderr.trim(), '', 'no output on stderr') 333 return fs.readdirAsync(path.join(cacheDir, '_cacache')) 334 }) 335 .then((modules) => { 336 t.ok(modules, 'should create _cacache folder') 337 t.end() 338 }) 339}) 340 341test('cleanup', () => { 342 SERVER.close() 343 return Promise.all([rimraf(cacheDir), rimraf(testDir)]) 344}) 345