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