summaryrefslogtreecommitdiff
path: root/deps/npm/lib/install/inflate-shrinkwrap.js
blob: 48be93d0952e72399342349aa7dc0597d551861c (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
205
'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, sw, opts, finishInflating) {
  if (!fetchPackageMetadata) {
    fetchPackageMetadata = BB.promisify(require('../fetch-package-metadata.js'))
    addBundled = BB.promisify(fetchPackageMetadata.addBundled)
  }
  if (arguments.length === 3) {
    finishInflating = opts
    opts = {}
  }
  if (!npm.config.get('shrinkwrap') || !npm.config.get('package-lock')) {
    return finishInflating()
  }
  tree.loaded = false
  tree.hasRequiresFromLock = sw.requires
  return inflateShrinkwrap(tree.path, tree, sw.dependencies, opts).then(
    () => finishInflating(),
    finishInflating
  )
}

function inflateShrinkwrap (topPath, tree, swdeps, opts) {
  if (!swdeps) return Promise.resolve()
  if (!opts) opts = {}
  const onDisk = {}
  tree.children.forEach((child) => {
    onDisk[moduleName(child)] = child
  })

  tree.children = []

  return BB.each(Object.keys(swdeps), (name) => {
    const sw = swdeps[name]
    const dependencies = sw.dependencies || {}
    const requested = realizeShrinkwrapSpecifier(name, sw, topPath)
    return inflatableChild(
      onDisk[name], name, topPath, tree, sw, requested, opts
    ).then((child) => {
      child.hasRequiresFromLock = tree.hasRequiresFromLock
      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
    onDiskChild.package._requested = requested
    onDiskChild.package._spec = requested.rawSpec
    onDiskChild.package._where = topPath
    onDiskChild.package._optional = sw.optional
    onDiskChild.package._development = sw.dev
    onDiskChild.package._inBundle = sw.bundled
    onDiskChild.fromBundle = (sw.bundled || onDiskChild.package._inBundle) ? 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)
    onDiskChild.swRequires = sw.requires
    tree.children.push(onDiskChild)
    return BB.resolve(onDiskChild)
  } else if ((sw.version && sw.integrity) || sw.bundled) {
    // 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,
    _id: name + '@' + sw.version,
    _resolved: adaptResolved(requested, sw.resolved),
    _requested: requested,
    _optional: sw.optional,
    _development: sw.dev,
    _inBundle: sw.bundled,
    _integrity: sw.integrity,
    _from: from,
    _spec: requested.rawSpec,
    _where: topPath,
    _args: [[requested.toString(), topPath]],
    dependencies: sw.requires
  }

  if (!sw.bundled) {
    const bundleDependencies = Object.keys(sw.dependencies || {}).filter((d) => sw.dependencies[d].bundled)
    if (bundleDependencies.length === 0) {
      pkg.bundleDependencies = bundleDependencies
    }
  }
  const child = createChild({
    package: pkg,
    loaded: true,
    parent: tree,
    children: [],
    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,
    isLink: requested.type === 'directory',
    isInLink: tree.isLink,
    swRequires: sw.requires
  })
  tree.children.push(child)
  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) {
  return fetchPackageMetadata(requested, topPath).then((pkg) => {
    pkg._from = sw.from || requested.raw
    pkg._optional = sw.optional
    pkg._development = sw.dev
    pkg._inBundle = false
    return addBundled(pkg).then(() => pkg)
  }).then((pkg) => {
    var isLink = pkg._requested.type === 'directory'
    const child = createChild({
      package: pkg,
      loaded: false,
      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,
      fromBundle: null,
      isLink: isLink,
      isInLink: tree.isLink,
      swRequires: sw.requires
    })
    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
}