• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1'use strict'
2const path = require('path')
3const fs = require('graceful-fs')
4const Bluebird = require('bluebird')
5const rimraf = Bluebird.promisify(require('rimraf'))
6const mkdirp = Bluebird.promisify(require('gentle-fs').mkdir)
7const lstat = Bluebird.promisify(fs.lstat)
8const readdir = Bluebird.promisify(fs.readdir)
9const symlink = Bluebird.promisify(fs.symlink)
10const gentlyRm = Bluebird.promisify(require('../../utils/gently-rm'))
11const moduleStagingPath = require('../module-staging-path.js')
12const move = require('move-concurrently')
13const moveOpts = {fs: fs, Promise: Bluebird, maxConcurrency: 4}
14const getRequested = require('../get-requested.js')
15const log = require('npmlog')
16const packageId = require('../../utils/package-id.js')
17
18module.exports = function (staging, pkg, log) {
19  log.silly('finalize', pkg.realpath)
20
21  const extractedTo = moduleStagingPath(staging, pkg)
22
23  const delpath = path.join(path.dirname(pkg.realpath), '.' + path.basename(pkg.realpath) + '.DELETE')
24  let movedDestAway = false
25
26  const requested = pkg.package._requested || getRequested(pkg)
27  if (requested.type === 'directory') {
28    const relative = path.relative(path.dirname(pkg.path), pkg.realpath)
29    return makeParentPath(pkg.path)
30      .then(() => symlink(relative, pkg.path, 'junction'))
31      .catch((ex) => {
32        return rimraf(pkg.path).then(() => symlink(relative, pkg.path, 'junction'))
33      })
34  } else {
35    return makeParentPath(pkg.realpath)
36      .then(moveStagingToDestination)
37      .then(restoreOldNodeModules)
38      .catch((err) => {
39        if (movedDestAway) {
40          return rimraf(pkg.realpath).then(moveOldDestinationBack).then(() => {
41            throw err
42          })
43        } else {
44          throw err
45        }
46      })
47      .then(() => rimraf(delpath))
48  }
49
50  function makeParentPath (dir) {
51    return mkdirp(path.dirname(dir))
52  }
53
54  function moveStagingToDestination () {
55    return destinationIsClear()
56      .then(actuallyMoveStaging)
57      .catch(() => moveOldDestinationAway().then(actuallyMoveStaging))
58  }
59
60  function destinationIsClear () {
61    return lstat(pkg.realpath).then(() => {
62      throw new Error('destination exists')
63    }, () => {})
64  }
65
66  function actuallyMoveStaging () {
67    return move(extractedTo, pkg.realpath, moveOpts)
68  }
69
70  function moveOldDestinationAway () {
71    return rimraf(delpath).then(() => {
72      return move(pkg.realpath, delpath, moveOpts)
73    }).then(() => { movedDestAway = true })
74  }
75
76  function moveOldDestinationBack () {
77    return move(delpath, pkg.realpath, moveOpts).then(() => { movedDestAway = false })
78  }
79
80  function restoreOldNodeModules () {
81    if (!movedDestAway) return
82    return readdir(path.join(delpath, 'node_modules')).catch(() => []).then((modules) => {
83      if (!modules.length) return
84      return mkdirp(path.join(pkg.realpath, 'node_modules')).then(() => Bluebird.map(modules, (file) => {
85        const from = path.join(delpath, 'node_modules', file)
86        const to = path.join(pkg.realpath, 'node_modules', file)
87        return move(from, to, moveOpts)
88      }))
89    })
90  }
91}
92
93module.exports.rollback = function (top, staging, pkg) {
94  return Bluebird.try(() => {
95    const requested = pkg.package._requested || getRequested(pkg)
96    if (requested && requested.type === 'directory') return Promise.resolve()
97    // strictly speaking rolling back a finalize should ONLY remove module that
98    // was being finalized, not any of the things under it. But currently
99    // those modules are guaranteed to be useless so we may as well remove them too.
100    // When/if we separate `commit` step and can rollback to previous versions
101    // of upgraded modules then we'll need to revisit this…
102    return gentlyRm(pkg.path, false, top).catch((err) => {
103      log.warn('rollback', `Rolling back ${packageId(pkg)} failed (this is probably harmless): ${err.message ? err.message : err}`)
104    })
105  })
106}
107