finalize.js 3.8 KB

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