summaryrefslogtreecommitdiff
path: root/deps/npm/lib/install/inflate-shrinkwrap.js
blob: 8cb75626bb5d7786f0f9dba05508ecb166657895 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
'use strict'

const BB = require('bluebird')

let addBundled
const childPath = require('../utils/child-path.js')
const createChild = require('./node.js').create
let fetchPackageMetadata
const inflateBundled = require('./inflate-bundled.js')
const moduleName = require('../utils/module-name.js')
const normalizePackageData = require('normalize-package-data')
const npm = require('../npm.js')
const realizeShrinkwrapSpecifier = require('./realize-shrinkwrap-specifier.js')
const validate = require('aproba')
const path = require('path')

module.exports = function (tree, swdeps, opts, finishInflating) {
  if (!fetchPackageMetadata) {
    fetchPackageMetadata = BB.promisify(require('../fetch-package-metadata.js'))
    addBundled = BB.promisify(fetchPackageMetadata.addBundled)
  }
  if (!npm.config.get('shrinkwrap') || !npm.config.get('package-lock')) {
    return finishInflating()
  }
  if (arguments.length === 3) {
    finishInflating = opts
    opts = {}
  }
  tree.loaded = true
  return inflateShrinkwrap(tree.path, tree, swdeps, opts).then(
    () => finishInflating(),
    finishInflating
  )
}

function inflateShrinkwrap (topPath, tree, swdeps, opts) {
  validate('SOO|SOOO', arguments)
  if (!opts) opts = {}
  const onDisk = {}
  tree.children.forEach((child) => {
    onDisk[moduleName(child)] = child
  })
  const dev = npm.config.get('dev') || (!/^prod(uction)?$/.test(npm.config.get('only')) && !npm.config.get('production')) || /^dev(elopment)?$/.test(npm.config.get('only'))
  const prod = !/^dev(elopment)?$/.test(npm.config.get('only'))

  tree.children = []

  return BB.each(Object.keys(swdeps), (name) => {
    const sw = swdeps[name]
    if (
      (!prod && !sw.dev) ||
      (!dev && sw.dev)
    ) { return null }
    const dependencies = sw.dependencies || {}
    const requested = realizeShrinkwrapSpecifier(name, sw, topPath)
    return inflatableChild(
      onDisk[name], name, topPath, tree, sw, requested, opts
    ).then((child) => {
      return inflateShrinkwrap(topPath, child, dependencies)
    })
  })
}

function normalizePackageDataNoErrors (pkg) {
  try {
    normalizePackageData(pkg)
  } catch (ex) {
    // don't care
  }
}

function inflatableChild (onDiskChild, name, topPath, tree, sw, requested, opts) {
  validate('OSSOOOO|ZSSOOOO', arguments)
  if (onDiskChild && childIsEquivalent(sw, requested, onDiskChild)) {
    // The version on disk matches the shrinkwrap entry.
    if (!onDiskChild.fromShrinkwrap) onDiskChild.fromShrinkwrap = true
    if (sw.dev) onDiskChild.shrinkwrapDev = true
    onDiskChild.package._requested = requested
    onDiskChild.package._spec = requested.rawSpec
    onDiskChild.package._where = topPath
    onDiskChild.fromBundle = sw.bundled ? tree.fromBundle || tree : null
    if (!onDiskChild.package._args) onDiskChild.package._args = []
    onDiskChild.package._args.push([String(requested), topPath])
    // non-npm registries can and will return unnormalized data, plus
    // even the npm registry may have package data normalized with older
    // normalization rules. This ensures we get package data in a consistent,
    // stable format.
    normalizePackageDataNoErrors(onDiskChild.package)
    tree.children.push(onDiskChild)
    return BB.resolve(onDiskChild)
  } else if (opts.fakeChildren !== false && sw.version && sw.integrity) {
    // The shrinkwrap entry has an integrity field. We can fake a pkg to get
    // the installer to do a content-address fetch from the cache, if possible.
    return BB.resolve(makeFakeChild(name, topPath, tree, sw, requested))
  } else {
    // It's not on disk, and we can't just look it up by address -- do a full
    // fpm/inflate bundle pass. For registry deps, this will go straight to the
    // tarball URL, as if it were a remote tarball dep.
    return fetchChild(topPath, tree, sw, requested)
  }
}

function makeFakeChild (name, topPath, tree, sw, requested) {
  const from = sw.from || requested.raw
  const pkg = {
    name: name,
    version: sw.version,
    _resolved: adaptResolved(requested, sw.resolved),
    _requested: requested,
    _optional: sw.optional,
    _integrity: sw.integrity,
    _from: from,
    _spec: requested.rawSpec,
    _where: topPath,
    _args: [[requested.toString(), topPath]]
  }
  let bundleAdded = BB.resolve()
  if (Object.keys(sw.dependencies || {}).some((d) => {
    return sw.dependencies[d].bundled
  })) {
    pkg.bundleDependencies = []
    bundleAdded = addBundled(pkg)
  }
  return bundleAdded.then(() => {
    const child = createChild({
      package: pkg,
      loaded: true,
      parent: tree,
      children: pkg._bundled || [],
      fromShrinkwrap: true,
      fakeChild: sw,
      fromBundle: sw.bundled ? tree.fromBundle || tree : null,
      path: childPath(tree.path, pkg),
      realpath: childPath(tree.realpath, pkg),
      location: tree.location + '/' + pkg.name,
      isInLink: tree.isLink
    })
    tree.children.push(child)
    if (pkg._bundled) {
      delete pkg._bundled
      inflateBundled(child, child, child.children)
    }
    return child
  })
}

function adaptResolved (requested, resolved) {
  const registry = requested.scope
  ? npm.config.get(`${requested.scope}:registry`) || npm.config.get('registry')
  : npm.config.get('registry')
  if (!requested.registry || (resolved && resolved.indexOf(registry) === 0)) {
    // Nothing to worry about here. Pass it through.
    return resolved
  } else {
    // We could fast-path for registry.npmjs.org here, but if we do, it
    // would end up getting written back to the `resolved` field. By always
    // returning `null` for other registries, `pacote.extract()` will take
    // care of any required metadata fetches internally, without altering
    // the tree we're going to write out to shrinkwrap/lockfile.
    return null
  }
}

function fetchChild (topPath, tree, sw, requested) {
  const from = sw.from || requested.raw
  const optional = sw.optional
  return fetchPackageMetadata(requested, topPath).then((pkg) => {
    pkg._from = from
    pkg._optional = optional
    return addBundled(pkg).then(() => pkg)
  }).then((pkg) => {
    var isLink = pkg._requested.type === 'directory'
    const child = createChild({
      package: pkg,
      loaded: true,
      parent: tree,
      fromShrinkwrap: requested,
      path: childPath(tree.path, pkg),
      realpath: isLink ? requested.fetchSpec : childPath(tree.realpath, pkg),
      children: pkg._bundled || [],
      location: tree.location + '/' + pkg.name,
      isLink: isLink,
      isInLink: tree.isLink
    })
    tree.children.push(child)
    if (pkg._bundled) {
      delete pkg._bundled
      inflateBundled(child, child, child.children)
    }
    return child
  })
}

function childIsEquivalent (sw, requested, child) {
  if (!child) return false
  if (child.fromShrinkwrap) return true
  if (sw.integrity && child.package._integrity === sw.integrity) return true
  if (child.isLink && requested.type === 'directory') return path.relative(child.realpath, requested.fetchSpec) === ''

  if (sw.resolved) return child.package._resolved === sw.resolved
  if (!requested.registry && sw.from) return child.package._from === sw.from
  if (!requested.registry && child.package._resolved) return sw.version === child.package._resolved
  return child.package.version === sw.version
}