• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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