1'use strict' 2 3const BB = require('bluebird') 4 5const fs = require('graceful-fs') 6const iferr = require('iferr') 7const inflateShrinkwrap = require('./inflate-shrinkwrap.js') 8const log = require('npmlog') 9const parseJSON = require('../utils/parse-json.js') 10const path = require('path') 11const PKGLOCK_VERSION = require('../npm.js').lockfileVersion 12 13const readFileAsync = BB.promisify(fs.readFile) 14 15module.exports = readShrinkwrap 16function readShrinkwrap (child, next) { 17 if (child.package._shrinkwrap) return process.nextTick(next) 18 BB.join( 19 maybeReadFile('npm-shrinkwrap.json', child), 20 // Don't read non-root lockfiles 21 child.isTop && maybeReadFile('package-lock.json', child), 22 (shrinkwrap, lockfile) => { 23 if (shrinkwrap && lockfile) { 24 log.warn('read-shrinkwrap', 'Ignoring package-lock.json because there is already an npm-shrinkwrap.json. Please use only one of the two.') 25 } 26 const name = shrinkwrap ? 'npm-shrinkwrap.json' : 'package-lock.json' 27 const parsed = parsePkgLock(shrinkwrap || lockfile, name) 28 if (parsed && parsed.lockfileVersion !== PKGLOCK_VERSION) { 29 log.warn('read-shrinkwrap', `This version of npm is compatible with lockfileVersion@${PKGLOCK_VERSION}, but ${name} was generated for lockfileVersion@${parsed.lockfileVersion || 0}. I'll try to do my best with it!`) 30 } 31 child.package._shrinkwrap = parsed 32 } 33 ).then(() => next(), next) 34} 35 36function maybeReadFile (name, child) { 37 return readFileAsync( 38 path.join(child.path, name), 39 'utf8' 40 ).catch({code: 'ENOENT'}, () => null) 41} 42 43module.exports.andInflate = function (child, next) { 44 readShrinkwrap(child, iferr(next, function () { 45 if (child.package._shrinkwrap) { 46 return inflateShrinkwrap(child, child.package._shrinkwrap || {}, next) 47 } else { 48 return next() 49 } 50 })) 51} 52 53const PARENT_RE = /\|{7,}/g 54const OURS_RE = /<{7,}/g 55const THEIRS_RE = /={7,}/g 56const END_RE = />{7,}/g 57 58module.exports._isDiff = isDiff 59function isDiff (str) { 60 return str.match(OURS_RE) && str.match(THEIRS_RE) && str.match(END_RE) 61} 62 63module.exports._parsePkgLock = parsePkgLock 64function parsePkgLock (str, filename) { 65 if (!str) { return null } 66 try { 67 return parseJSON(str) 68 } catch (e) { 69 if (isDiff(str)) { 70 log.warn('conflict', `A git conflict was detected in ${filename}. Attempting to auto-resolve.`) 71 log.warn('conflict', 'To make this happen automatically on git rebase/merge, consider using the npm-merge-driver:') 72 log.warn('conflict', '$ npx npm-merge-driver install -g') 73 const pieces = str.split(/[\n\r]+/g).reduce((acc, line) => { 74 if (line.match(PARENT_RE)) acc.state = 'parent' 75 else if (line.match(OURS_RE)) acc.state = 'ours' 76 else if (line.match(THEIRS_RE)) acc.state = 'theirs' 77 else if (line.match(END_RE)) acc.state = 'top' 78 else { 79 if (acc.state === 'top' || acc.state === 'ours') acc.ours += line 80 if (acc.state === 'top' || acc.state === 'theirs') acc.theirs += line 81 if (acc.state === 'top' || acc.state === 'parent') acc.parent += line 82 } 83 return acc 84 }, { 85 state: 'top', 86 ours: '', 87 theirs: '', 88 parent: '' 89 }) 90 try { 91 const ours = parseJSON(pieces.ours) 92 const theirs = parseJSON(pieces.theirs) 93 return reconcileLockfiles(ours, theirs) 94 } catch (_e) { 95 log.error('conflict', `Automatic conflict resolution failed. Please manually resolve conflicts in ${filename} and try again.`) 96 log.silly('conflict', `Error during resolution: ${_e}`) 97 throw e 98 } 99 } else { 100 throw e 101 } 102 } 103} 104 105function reconcileLockfiles (parent, ours, theirs) { 106 return Object.assign({}, ours, theirs) 107} 108