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