save.js 5.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193
  1. 'use strict'
  2. const deepSortObject = require('../utils/deep-sort-object.js')
  3. const detectIndent = require('detect-indent')
  4. const detectNewline = require('detect-newline')
  5. const fs = require('graceful-fs')
  6. const iferr = require('iferr')
  7. const log = require('npmlog')
  8. const moduleName = require('../utils/module-name.js')
  9. const npm = require('../npm.js')
  10. const packageId = require('../utils/package-id.js')
  11. const parseJSON = require('../utils/parse-json.js')
  12. const path = require('path')
  13. const stringifyPackage = require('stringify-package')
  14. const validate = require('aproba')
  15. const without = require('lodash.without')
  16. const writeFileAtomic = require('write-file-atomic')
  17. // if the -S|--save option is specified, then write installed packages
  18. // as dependencies to a package.json file.
  19. exports.saveRequested = function (tree, andReturn) {
  20. validate('OF', arguments)
  21. savePackageJson(tree, andWarnErrors(andSaveShrinkwrap(tree, andReturn)))
  22. }
  23. function andSaveShrinkwrap (tree, andReturn) {
  24. validate('OF', arguments)
  25. return function (er) {
  26. validate('E', arguments)
  27. saveShrinkwrap(tree, andWarnErrors(andReturn))
  28. }
  29. }
  30. function andWarnErrors (cb) {
  31. validate('F', arguments)
  32. return function (er) {
  33. if (er) log.warn('saveError', er.message)
  34. arguments[0] = null
  35. cb.apply(null, arguments)
  36. }
  37. }
  38. exports.saveShrinkwrap = saveShrinkwrap
  39. function saveShrinkwrap (tree, next) {
  40. validate('OF', arguments)
  41. if (!npm.config.get('package-lock-only') && (!npm.config.get('shrinkwrap') || !npm.config.get('package-lock'))) {
  42. return next()
  43. }
  44. require('../shrinkwrap.js').createShrinkwrap(tree, {silent: false}, next)
  45. }
  46. function savePackageJson (tree, next) {
  47. validate('OF', arguments)
  48. var saveBundle = npm.config.get('save-bundle')
  49. // each item in the tree is a top-level thing that should be saved
  50. // to the package.json file.
  51. // The relevant tree shape is { <folder>: {what:<pkg>} }
  52. var saveTarget = path.resolve(tree.path, 'package.json')
  53. // don't use readJson, because we don't want to do all the other
  54. // tricky npm-specific stuff that's in there.
  55. fs.readFile(saveTarget, 'utf8', iferr(next, function (packagejson) {
  56. const indent = detectIndent(packagejson).indent
  57. const newline = detectNewline(packagejson)
  58. try {
  59. tree.package = parseJSON(packagejson)
  60. } catch (ex) {
  61. return next(ex)
  62. }
  63. // If we're saving bundled deps, normalize the key before we start
  64. if (saveBundle) {
  65. var bundle = tree.package.bundleDependencies || tree.package.bundledDependencies
  66. delete tree.package.bundledDependencies
  67. if (!Array.isArray(bundle)) bundle = []
  68. }
  69. var toSave = getThingsToSave(tree)
  70. var toRemove = getThingsToRemove(tree)
  71. var savingTo = {}
  72. toSave.forEach(function (pkg) { if (pkg.save) savingTo[pkg.save] = true })
  73. toRemove.forEach(function (pkg) { if (pkg.save) savingTo[pkg.save] = true })
  74. Object.keys(savingTo).forEach(function (save) {
  75. if (!tree.package[save]) tree.package[save] = {}
  76. })
  77. log.verbose('saving', toSave)
  78. const types = ['dependencies', 'devDependencies', 'optionalDependencies']
  79. toSave.forEach(function (pkg) {
  80. if (pkg.save) tree.package[pkg.save][pkg.name] = pkg.spec
  81. const movedFrom = []
  82. for (let saveType of types) {
  83. if (
  84. pkg.save !== saveType &&
  85. tree.package[saveType] &&
  86. tree.package[saveType][pkg.name]
  87. ) {
  88. movedFrom.push(saveType)
  89. delete tree.package[saveType][pkg.name]
  90. }
  91. }
  92. if (movedFrom.length) {
  93. log.notice('save', `${pkg.name} is being moved from ${movedFrom.join(' and ')} to ${pkg.save}`)
  94. }
  95. if (saveBundle) {
  96. var ii = bundle.indexOf(pkg.name)
  97. if (ii === -1) bundle.push(pkg.name)
  98. }
  99. })
  100. toRemove.forEach(function (pkg) {
  101. if (pkg.save) delete tree.package[pkg.save][pkg.name]
  102. if (saveBundle) {
  103. bundle = without(bundle, pkg.name)
  104. }
  105. })
  106. Object.keys(savingTo).forEach(function (key) {
  107. tree.package[key] = deepSortObject(tree.package[key])
  108. })
  109. if (saveBundle) {
  110. tree.package.bundleDependencies = deepSortObject(bundle)
  111. }
  112. var json = stringifyPackage(tree.package, indent, newline)
  113. if (json === packagejson) {
  114. log.verbose('shrinkwrap', 'skipping write for package.json because there were no changes.')
  115. next()
  116. } else {
  117. writeFileAtomic(saveTarget, json, next)
  118. }
  119. // Restore derived id as it was removed when reloading from disk
  120. tree.package._id = packageId(tree.package)
  121. }))
  122. }
  123. exports.getSaveType = function (tree, arg) {
  124. if (arguments.length) validate('OO', arguments)
  125. var globalInstall = npm.config.get('global')
  126. var noSaveFlags = !npm.config.get('save') &&
  127. !npm.config.get('save-dev') &&
  128. !npm.config.get('save-prod') &&
  129. !npm.config.get('save-optional')
  130. if (globalInstall || noSaveFlags) return null
  131. if (npm.config.get('save-optional')) {
  132. return 'optionalDependencies'
  133. } else if (npm.config.get('save-dev')) {
  134. return 'devDependencies'
  135. } else if (npm.config.get('save-prod')) {
  136. return 'dependencies'
  137. } else {
  138. if (arg) {
  139. var name = moduleName(arg)
  140. if (tree.package.optionalDependencies[name]) {
  141. return 'optionalDependencies'
  142. } else if (tree.package.devDependencies[name]) {
  143. return 'devDependencies'
  144. }
  145. }
  146. return 'dependencies'
  147. }
  148. }
  149. function getThingsToSave (tree) {
  150. validate('O', arguments)
  151. var toSave = tree.children.filter(function (child) {
  152. return child.save
  153. }).map(function (child) {
  154. return {
  155. name: moduleName(child),
  156. spec: child.saveSpec,
  157. save: child.save
  158. }
  159. })
  160. return toSave
  161. }
  162. function getThingsToRemove (tree) {
  163. validate('O', arguments)
  164. if (!tree.removedChildren) return []
  165. var toRemove = tree.removedChildren.map(function (child) {
  166. return {
  167. name: moduleName(child),
  168. save: child.save
  169. }
  170. })
  171. return toRemove
  172. }