• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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