@npmcli/arborist 7.4.2 → 7.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/index.js +5 -4
- package/bin/lib/logging.js +1 -1
- package/bin/lib/timers.js +16 -16
- package/lib/add-rm-pkg-deps.js +1 -1
- package/lib/arborist/build-ideal-tree.js +18 -19
- package/lib/arborist/index.js +31 -6
- package/lib/arborist/isolated-reifier.js +3 -3
- package/lib/arborist/load-actual.js +2 -2
- package/lib/arborist/load-virtual.js +1 -1
- package/lib/arborist/rebuild.js +16 -20
- package/lib/arborist/reify.js +292 -365
- package/lib/audit-report.js +6 -5
- package/lib/dep-valid.js +1 -1
- package/lib/inventory.js +1 -1
- package/lib/place-dep.js +1 -1
- package/lib/query-selector-all.js +7 -7
- package/lib/shrinkwrap.js +3 -1
- package/lib/tracker.js +13 -28
- package/package.json +12 -12
package/lib/arborist/reify.js
CHANGED
|
@@ -7,7 +7,7 @@ const npa = require('npm-package-arg')
|
|
|
7
7
|
const semver = require('semver')
|
|
8
8
|
const debug = require('../debug.js')
|
|
9
9
|
const { walkUp } = require('walk-up-path')
|
|
10
|
-
const log = require('proc-log')
|
|
10
|
+
const { log, time } = require('proc-log')
|
|
11
11
|
const hgi = require('hosted-git-info')
|
|
12
12
|
const rpj = require('read-package-json-fast')
|
|
13
13
|
|
|
@@ -38,119 +38,96 @@ const { saveTypeMap, hasSubKey } = require('../add-rm-pkg-deps.js')
|
|
|
38
38
|
const Shrinkwrap = require('../shrinkwrap.js')
|
|
39
39
|
const { defaultLockfileVersion } = Shrinkwrap
|
|
40
40
|
|
|
41
|
-
|
|
42
|
-
const _retiredUnchanged = Symbol('retiredUnchanged')
|
|
43
|
-
const _sparseTreeDirs = Symbol('sparseTreeDirs')
|
|
44
|
-
const _sparseTreeRoots = Symbol('sparseTreeRoots')
|
|
45
|
-
const _savePrefix = Symbol('savePrefix')
|
|
41
|
+
// Part of steps (steps need refactoring before we can do anything about these)
|
|
46
42
|
const _retireShallowNodes = Symbol.for('retireShallowNodes')
|
|
47
|
-
const
|
|
48
|
-
const
|
|
49
|
-
const
|
|
43
|
+
const _loadBundlesAndUpdateTrees = Symbol.for('loadBundlesAndUpdateTrees')
|
|
44
|
+
const _submitQuickAudit = Symbol('submitQuickAudit')
|
|
45
|
+
const _addOmitsToTrashList = Symbol('addOmitsToTrashList')
|
|
46
|
+
const _unpackNewModules = Symbol.for('unpackNewModules')
|
|
47
|
+
const _build = Symbol.for('build')
|
|
50
48
|
|
|
51
49
|
// shared by rebuild mixin
|
|
52
50
|
const _trashList = Symbol.for('trashList')
|
|
53
51
|
const _handleOptionalFailure = Symbol.for('handleOptionalFailure')
|
|
54
52
|
const _loadTrees = Symbol.for('loadTrees')
|
|
53
|
+
// defined by rebuild mixin
|
|
54
|
+
const _checkBins = Symbol.for('checkBins')
|
|
55
55
|
|
|
56
56
|
// shared symbols for swapping out when testing
|
|
57
|
+
// TODO tests should not be this deep into internals
|
|
57
58
|
const _diffTrees = Symbol.for('diffTrees')
|
|
58
59
|
const _createSparseTree = Symbol.for('createSparseTree')
|
|
59
60
|
const _loadShrinkwrapsAndUpdateTrees = Symbol.for('loadShrinkwrapsAndUpdateTrees')
|
|
60
|
-
const _shrinkwrapInflated = Symbol('shrinkwrapInflated')
|
|
61
|
-
const _bundleUnpacked = Symbol('bundleUnpacked')
|
|
62
|
-
const _bundleMissing = Symbol('bundleMissing')
|
|
63
61
|
const _reifyNode = Symbol.for('reifyNode')
|
|
64
|
-
const _extractOrLink = Symbol('extractOrLink')
|
|
65
62
|
const _updateAll = Symbol.for('updateAll')
|
|
66
63
|
const _updateNames = Symbol.for('updateNames')
|
|
67
|
-
// defined by rebuild mixin
|
|
68
|
-
const _checkBins = Symbol.for('checkBins')
|
|
69
|
-
const _symlink = Symbol('symlink')
|
|
70
|
-
const _warnDeprecated = Symbol('warnDeprecated')
|
|
71
|
-
const _loadBundlesAndUpdateTrees = Symbol.for('loadBundlesAndUpdateTrees')
|
|
72
|
-
const _submitQuickAudit = Symbol('submitQuickAudit')
|
|
73
|
-
const _unpackNewModules = Symbol.for('unpackNewModules')
|
|
74
64
|
const _moveContents = Symbol.for('moveContents')
|
|
75
65
|
const _moveBackRetiredUnchanged = Symbol.for('moveBackRetiredUnchanged')
|
|
76
|
-
const _build = Symbol.for('build')
|
|
77
66
|
const _removeTrash = Symbol.for('removeTrash')
|
|
78
67
|
const _renamePath = Symbol.for('renamePath')
|
|
79
68
|
const _rollbackRetireShallowNodes = Symbol.for('rollbackRetireShallowNodes')
|
|
80
69
|
const _rollbackCreateSparseTree = Symbol.for('rollbackCreateSparseTree')
|
|
81
70
|
const _rollbackMoveBackRetiredUnchanged = Symbol.for('rollbackMoveBackRetiredUnchanged')
|
|
82
71
|
const _saveIdealTree = Symbol.for('saveIdealTree')
|
|
83
|
-
const _copyIdealToActual = Symbol('copyIdealToActual')
|
|
84
|
-
const _addOmitsToTrashList = Symbol('addOmitsToTrashList')
|
|
85
|
-
const _packageLockOnly = Symbol('packageLockOnly')
|
|
86
|
-
const _dryRun = Symbol('dryRun')
|
|
87
|
-
const _validateNodeModules = Symbol('validateNodeModules')
|
|
88
|
-
const _nmValidated = Symbol('nmValidated')
|
|
89
|
-
const _validatePath = Symbol('validatePath')
|
|
90
72
|
const _reifyPackages = Symbol.for('reifyPackages')
|
|
91
73
|
|
|
92
|
-
|
|
93
|
-
const _omitOptional = Symbol('omitOptional')
|
|
94
|
-
const _omitPeer = Symbol('omitPeer')
|
|
95
|
-
|
|
96
|
-
const _pruneBundledMetadeps = Symbol('pruneBundledMetadeps')
|
|
97
|
-
|
|
98
|
-
// defined by Ideal mixin
|
|
74
|
+
// defined by build-ideal-tree mixin
|
|
99
75
|
const _resolvedAdd = Symbol.for('resolvedAdd')
|
|
100
76
|
const _usePackageLock = Symbol.for('usePackageLock')
|
|
101
|
-
|
|
77
|
+
// used by build-ideal-tree mixin
|
|
78
|
+
const _addNodeToTrashList = Symbol.for('addNodeToTrashList')
|
|
102
79
|
|
|
103
80
|
const _createIsolatedTree = Symbol.for('createIsolatedTree')
|
|
104
81
|
|
|
105
82
|
module.exports = cls => class Reifier extends cls {
|
|
83
|
+
#bundleMissing = new Set() // child nodes we'd EXPECT to be included in a bundle, but aren't
|
|
84
|
+
#bundleUnpacked = new Set() // the nodes we unpack to read their bundles
|
|
85
|
+
#dryRun
|
|
86
|
+
#nmValidated = new Set()
|
|
87
|
+
#omitDev
|
|
88
|
+
#omitPeer
|
|
89
|
+
#omitOptional
|
|
90
|
+
#retiredPaths = {}
|
|
91
|
+
#retiredUnchanged = {}
|
|
92
|
+
#savePrefix
|
|
93
|
+
#shrinkwrapInflated = new Set()
|
|
94
|
+
#sparseTreeDirs = new Set()
|
|
95
|
+
#sparseTreeRoots = new Set()
|
|
96
|
+
|
|
106
97
|
constructor (options) {
|
|
107
98
|
super(options)
|
|
108
99
|
|
|
109
|
-
const {
|
|
110
|
-
savePrefix = '^',
|
|
111
|
-
packageLockOnly = false,
|
|
112
|
-
dryRun = false,
|
|
113
|
-
formatPackageLock = true,
|
|
114
|
-
} = options
|
|
115
|
-
|
|
116
|
-
this[_dryRun] = !!dryRun
|
|
117
|
-
this[_packageLockOnly] = !!packageLockOnly
|
|
118
|
-
this[_savePrefix] = savePrefix
|
|
119
|
-
this[_formatPackageLock] = !!formatPackageLock
|
|
120
|
-
|
|
121
|
-
this.diff = null
|
|
122
|
-
this[_retiredPaths] = {}
|
|
123
|
-
this[_shrinkwrapInflated] = new Set()
|
|
124
|
-
this[_retiredUnchanged] = {}
|
|
125
|
-
this[_sparseTreeDirs] = new Set()
|
|
126
|
-
this[_sparseTreeRoots] = new Set()
|
|
127
100
|
this[_trashList] = new Set()
|
|
128
|
-
// the nodes we unpack to read their bundles
|
|
129
|
-
this[_bundleUnpacked] = new Set()
|
|
130
|
-
// child nodes we'd EXPECT to be included in a bundle, but aren't
|
|
131
|
-
this[_bundleMissing] = new Set()
|
|
132
|
-
this[_nmValidated] = new Set()
|
|
133
101
|
}
|
|
134
102
|
|
|
135
103
|
// public method
|
|
136
104
|
async reify (options = {}) {
|
|
137
105
|
const linked = (options.installStrategy || this.options.installStrategy) === 'linked'
|
|
138
106
|
|
|
139
|
-
if (this
|
|
107
|
+
if (this.options.packageLockOnly && this.options.global) {
|
|
140
108
|
const er = new Error('cannot generate lockfile for global packages')
|
|
141
109
|
er.code = 'ESHRINKWRAPGLOBAL'
|
|
142
110
|
throw er
|
|
143
111
|
}
|
|
144
112
|
|
|
145
113
|
const omit = new Set(options.omit || [])
|
|
146
|
-
this
|
|
147
|
-
this
|
|
148
|
-
this
|
|
114
|
+
this.#omitDev = omit.has('dev')
|
|
115
|
+
this.#omitOptional = omit.has('optional')
|
|
116
|
+
this.#omitPeer = omit.has('peer')
|
|
149
117
|
|
|
150
118
|
// start tracker block
|
|
151
119
|
this.addTracker('reify')
|
|
152
|
-
|
|
153
|
-
|
|
120
|
+
const timeEnd = time.start('reify')
|
|
121
|
+
// don't create missing dirs on dry runs
|
|
122
|
+
if (!this.options.packageLockOnly && !this.options.dryRun) {
|
|
123
|
+
// we do NOT want to set ownership on this folder, especially
|
|
124
|
+
// recursively, because it can have other side effects to do that
|
|
125
|
+
// in a project directory. We just want to make it if it's missing.
|
|
126
|
+
await mkdir(resolve(this.path), { recursive: true })
|
|
127
|
+
|
|
128
|
+
// do not allow the top-level node_modules to be a symlink
|
|
129
|
+
await this.#validateNodeModules(resolve(this.path, 'node_modules'))
|
|
130
|
+
}
|
|
154
131
|
await this[_loadTrees](options)
|
|
155
132
|
|
|
156
133
|
const oldTree = this.idealTree
|
|
@@ -159,7 +136,7 @@ module.exports = cls => class Reifier extends cls {
|
|
|
159
136
|
// this is currently technical debt which will be resolved in a refactor
|
|
160
137
|
// of Node/Link trees
|
|
161
138
|
log.warn('reify', 'The "linked" install strategy is EXPERIMENTAL and may contain bugs.')
|
|
162
|
-
this.idealTree = await this[_createIsolatedTree](
|
|
139
|
+
this.idealTree = await this[_createIsolatedTree]()
|
|
163
140
|
}
|
|
164
141
|
await this[_diffTrees]()
|
|
165
142
|
await this[_reifyPackages]()
|
|
@@ -169,37 +146,139 @@ module.exports = cls => class Reifier extends cls {
|
|
|
169
146
|
this.idealTree = oldTree
|
|
170
147
|
}
|
|
171
148
|
await this[_saveIdealTree](options)
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
149
|
+
// clean up any trash that is still in the tree
|
|
150
|
+
for (const path of this[_trashList]) {
|
|
151
|
+
const loc = relpath(this.idealTree.realpath, path)
|
|
152
|
+
const node = this.idealTree.inventory.get(loc)
|
|
153
|
+
if (node && node.root === this.idealTree) {
|
|
154
|
+
node.parent = null
|
|
155
|
+
}
|
|
156
|
+
}
|
|
175
157
|
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
158
|
+
// if we filtered to only certain nodes, then anything ELSE needs
|
|
159
|
+
// to be untouched in the resulting actual tree, even if it differs
|
|
160
|
+
// in the idealTree. Copy over anything that was in the actual and
|
|
161
|
+
// was not changed, delete anything in the ideal and not actual.
|
|
162
|
+
// Then we move the entire idealTree over to this.actualTree, and
|
|
163
|
+
// save the hidden lockfile.
|
|
164
|
+
if (this.diff && this.diff.filterSet.size) {
|
|
165
|
+
const reroot = new Set()
|
|
180
166
|
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
167
|
+
const { filterSet } = this.diff
|
|
168
|
+
const seen = new Set()
|
|
169
|
+
for (const [loc, ideal] of this.idealTree.inventory.entries()) {
|
|
170
|
+
seen.add(loc)
|
|
171
|
+
|
|
172
|
+
// if it's an ideal node from the filter set, then skip it
|
|
173
|
+
// because we already made whatever changes were necessary
|
|
174
|
+
if (filterSet.has(ideal)) {
|
|
175
|
+
continue
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// otherwise, if it's not in the actualTree, then it's not a thing
|
|
179
|
+
// that we actually added. And if it IS in the actualTree, then
|
|
180
|
+
// it's something that we left untouched, so we need to record
|
|
181
|
+
// that.
|
|
182
|
+
const actual = this.actualTree.inventory.get(loc)
|
|
183
|
+
if (!actual) {
|
|
184
|
+
ideal.root = null
|
|
185
|
+
} else {
|
|
186
|
+
if ([...actual.linksIn].some(link => filterSet.has(link))) {
|
|
187
|
+
seen.add(actual.location)
|
|
188
|
+
continue
|
|
189
|
+
}
|
|
190
|
+
const { realpath, isLink } = actual
|
|
191
|
+
if (isLink && ideal.isLink && ideal.realpath === realpath) {
|
|
192
|
+
continue
|
|
193
|
+
} else {
|
|
194
|
+
reroot.add(actual)
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// now find any actual nodes that may not be present in the ideal
|
|
200
|
+
// tree, but were left behind by virtue of not being in the filter
|
|
201
|
+
for (const [loc, actual] of this.actualTree.inventory.entries()) {
|
|
202
|
+
if (seen.has(loc)) {
|
|
203
|
+
continue
|
|
204
|
+
}
|
|
205
|
+
seen.add(loc)
|
|
206
|
+
|
|
207
|
+
// we know that this is something that ISN'T in the idealTree,
|
|
208
|
+
// or else we will have addressed it in the previous loop.
|
|
209
|
+
// If it's in the filterSet, that means we intentionally removed
|
|
210
|
+
// it, so nothing to do here.
|
|
211
|
+
if (filterSet.has(actual)) {
|
|
212
|
+
continue
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
reroot.add(actual)
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
// go through the rerooted actual nodes, and move them over.
|
|
219
|
+
for (const actual of reroot) {
|
|
220
|
+
actual.root = this.idealTree
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
// prune out any tops that lack a linkIn, they are no longer relevant.
|
|
224
|
+
for (const top of this.idealTree.tops) {
|
|
225
|
+
if (top.linksIn.size === 0) {
|
|
226
|
+
top.root = null
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
// need to calculate dep flags, since nodes may have been marked
|
|
231
|
+
// as extraneous or otherwise incorrect during transit.
|
|
232
|
+
calcDepFlags(this.idealTree)
|
|
185
233
|
}
|
|
186
234
|
|
|
187
|
-
//
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
235
|
+
// save the ideal's meta as a hidden lockfile after we actualize it
|
|
236
|
+
this.idealTree.meta.filename =
|
|
237
|
+
this.idealTree.realpath + '/node_modules/.package-lock.json'
|
|
238
|
+
this.idealTree.meta.hiddenLockfile = true
|
|
239
|
+
this.idealTree.meta.lockfileVersion = defaultLockfileVersion
|
|
240
|
+
|
|
241
|
+
this.actualTree = this.idealTree
|
|
242
|
+
this.idealTree = null
|
|
243
|
+
|
|
244
|
+
if (!this.options.global) {
|
|
245
|
+
await this.actualTree.meta.save()
|
|
246
|
+
const ignoreScripts = !!this.options.ignoreScripts
|
|
247
|
+
// if we aren't doing a dry run or ignoring scripts and we actually made changes to the dep
|
|
248
|
+
// tree, then run the dependencies scripts
|
|
249
|
+
if (!this.options.dryRun && !ignoreScripts && this.diff && this.diff.children.length) {
|
|
250
|
+
const { path, package: pkg } = this.actualTree.target
|
|
251
|
+
const stdio = this.options.foregroundScripts ? 'inherit' : 'pipe'
|
|
252
|
+
const { scripts = {} } = pkg
|
|
253
|
+
for (const event of ['predependencies', 'dependencies', 'postdependencies']) {
|
|
254
|
+
if (Object.prototype.hasOwnProperty.call(scripts, event)) {
|
|
255
|
+
log.info('run', pkg._id, event, scripts[event])
|
|
256
|
+
await time.start(`reify:run:${event}`, () => runScript({
|
|
257
|
+
event,
|
|
258
|
+
path,
|
|
259
|
+
pkg,
|
|
260
|
+
stdio,
|
|
261
|
+
scriptShell: this.options.scriptShell,
|
|
262
|
+
}))
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
// This is a very bad pattern and I can't wait to stop doing it
|
|
268
|
+
this.auditReport = await this.auditReport
|
|
191
269
|
|
|
192
|
-
|
|
193
|
-
|
|
270
|
+
this.finishTracker('reify')
|
|
271
|
+
timeEnd()
|
|
272
|
+
return treeCheck(this.actualTree)
|
|
194
273
|
}
|
|
195
274
|
|
|
196
275
|
async [_reifyPackages] () {
|
|
197
276
|
// we don't submit the audit report or write to disk on dry runs
|
|
198
|
-
if (this
|
|
277
|
+
if (this.options.dryRun) {
|
|
199
278
|
return
|
|
200
279
|
}
|
|
201
280
|
|
|
202
|
-
if (this
|
|
281
|
+
if (this.options.packageLockOnly) {
|
|
203
282
|
// we already have the complete tree, so just audit it now,
|
|
204
283
|
// and that's all we have to do here.
|
|
205
284
|
return this[_submitQuickAudit]()
|
|
@@ -248,6 +327,7 @@ module.exports = cls => class Reifier extends cls {
|
|
|
248
327
|
throw reifyTerminated
|
|
249
328
|
}
|
|
250
329
|
} catch (er) {
|
|
330
|
+
// TODO rollbacks shouldn't be relied on to throw err
|
|
251
331
|
await this[rollback](er)
|
|
252
332
|
/* istanbul ignore next - rollback throws, should never hit this */
|
|
253
333
|
throw er
|
|
@@ -269,16 +349,15 @@ module.exports = cls => class Reifier extends cls {
|
|
|
269
349
|
// when doing a local install, we load everything and figure it all out.
|
|
270
350
|
// when doing a global install, we *only* care about the explicit requests.
|
|
271
351
|
[_loadTrees] (options) {
|
|
272
|
-
|
|
352
|
+
const timeEnd = time.start('reify:loadTrees')
|
|
273
353
|
const bitOpt = {
|
|
274
354
|
...options,
|
|
275
|
-
complete: this
|
|
355
|
+
complete: this.options.packageLockOnly || this.options.dryRun,
|
|
276
356
|
}
|
|
277
357
|
|
|
278
358
|
// if we're only writing a package lock, then it doesn't matter what's here
|
|
279
|
-
if (this
|
|
280
|
-
return this.buildIdealTree(bitOpt)
|
|
281
|
-
.then(() => process.emit('timeEnd', 'reify:loadTrees'))
|
|
359
|
+
if (this.options.packageLockOnly) {
|
|
360
|
+
return this.buildIdealTree(bitOpt).then(timeEnd)
|
|
282
361
|
}
|
|
283
362
|
|
|
284
363
|
const actualOpt = this.options.global ? {
|
|
@@ -312,7 +391,7 @@ module.exports = cls => class Reifier extends cls {
|
|
|
312
391
|
return Promise.all([
|
|
313
392
|
this.loadActual(actualOpt),
|
|
314
393
|
this.buildIdealTree(bitOpt),
|
|
315
|
-
]).then(
|
|
394
|
+
]).then(timeEnd)
|
|
316
395
|
}
|
|
317
396
|
|
|
318
397
|
// the global install space tends to have a lot of stuff in it. don't
|
|
@@ -322,15 +401,15 @@ module.exports = cls => class Reifier extends cls {
|
|
|
322
401
|
// explicitRequests which is set during buildIdealTree
|
|
323
402
|
return this.buildIdealTree(bitOpt)
|
|
324
403
|
.then(() => this.loadActual(actualOpt))
|
|
325
|
-
.then(
|
|
404
|
+
.then(timeEnd)
|
|
326
405
|
}
|
|
327
406
|
|
|
328
407
|
[_diffTrees] () {
|
|
329
|
-
if (this
|
|
408
|
+
if (this.options.packageLockOnly) {
|
|
330
409
|
return
|
|
331
410
|
}
|
|
332
411
|
|
|
333
|
-
|
|
412
|
+
const timeEnd = time.start('reify:diffTrees')
|
|
334
413
|
// XXX if we have an existing diff already, there should be a way
|
|
335
414
|
// to just invalidate the parts that changed, but avoid walking the
|
|
336
415
|
// whole tree again.
|
|
@@ -384,7 +463,7 @@ module.exports = cls => class Reifier extends cls {
|
|
|
384
463
|
// find all the nodes that need to change between the actual
|
|
385
464
|
// and ideal trees.
|
|
386
465
|
this.diff = Diff.calculate({
|
|
387
|
-
shrinkwrapInflated: this
|
|
466
|
+
shrinkwrapInflated: this.#shrinkwrapInflated,
|
|
388
467
|
filterNodes,
|
|
389
468
|
actual: this.actualTree,
|
|
390
469
|
ideal: this.idealTree,
|
|
@@ -397,7 +476,7 @@ module.exports = cls => class Reifier extends cls {
|
|
|
397
476
|
// because if we remove node_modules/FOO on case-insensitive systems,
|
|
398
477
|
// it will remove the dep that we *want* at node_modules/foo.
|
|
399
478
|
|
|
400
|
-
|
|
479
|
+
timeEnd()
|
|
401
480
|
}
|
|
402
481
|
|
|
403
482
|
// add the node and all its bins to the list of things to be
|
|
@@ -406,7 +485,7 @@ module.exports = cls => class Reifier extends cls {
|
|
|
406
485
|
// replace them when rolling back on failure.
|
|
407
486
|
[_addNodeToTrashList] (node, retire = false) {
|
|
408
487
|
const paths = [node.path, ...node.binPaths]
|
|
409
|
-
const moves = this
|
|
488
|
+
const moves = this.#retiredPaths
|
|
410
489
|
log.silly('reify', 'mark', retire ? 'retired' : 'deleted', paths)
|
|
411
490
|
for (const path of paths) {
|
|
412
491
|
if (retire) {
|
|
@@ -422,8 +501,8 @@ module.exports = cls => class Reifier extends cls {
|
|
|
422
501
|
// move aside the shallowest nodes in the tree that have to be
|
|
423
502
|
// changed or removed, so that we can rollback if necessary.
|
|
424
503
|
[_retireShallowNodes] () {
|
|
425
|
-
|
|
426
|
-
const moves = this
|
|
504
|
+
const timeEnd = time.start('reify:retireShallow')
|
|
505
|
+
const moves = this.#retiredPaths = {}
|
|
427
506
|
for (const diff of this.diff.children) {
|
|
428
507
|
if (diff.action === 'CHANGE' || diff.action === 'REMOVE') {
|
|
429
508
|
// we'll have to clean these up at the end, so add them to the list
|
|
@@ -433,8 +512,7 @@ module.exports = cls => class Reifier extends cls {
|
|
|
433
512
|
log.silly('reify', 'moves', moves)
|
|
434
513
|
const movePromises = Object.entries(moves)
|
|
435
514
|
.map(([from, to]) => this[_renamePath](from, to))
|
|
436
|
-
return promiseAllRejectLate(movePromises)
|
|
437
|
-
.then(() => process.emit('timeEnd', 'reify:retireShallow'))
|
|
515
|
+
return promiseAllRejectLate(movePromises).then(timeEnd)
|
|
438
516
|
}
|
|
439
517
|
|
|
440
518
|
[_renamePath] (from, to, didMkdirp = false) {
|
|
@@ -456,14 +534,14 @@ module.exports = cls => class Reifier extends cls {
|
|
|
456
534
|
}
|
|
457
535
|
|
|
458
536
|
[_rollbackRetireShallowNodes] (er) {
|
|
459
|
-
|
|
460
|
-
const moves = this
|
|
537
|
+
const timeEnd = time.start('reify:rollback:retireShallow')
|
|
538
|
+
const moves = this.#retiredPaths
|
|
461
539
|
const movePromises = Object.entries(moves)
|
|
462
540
|
.map(([from, to]) => this[_renamePath](to, from))
|
|
463
541
|
return promiseAllRejectLate(movePromises)
|
|
464
542
|
// ignore subsequent rollback errors
|
|
465
|
-
.catch(
|
|
466
|
-
.then(
|
|
543
|
+
.catch(() => {})
|
|
544
|
+
.then(timeEnd)
|
|
467
545
|
.then(() => {
|
|
468
546
|
throw er
|
|
469
547
|
})
|
|
@@ -472,11 +550,11 @@ module.exports = cls => class Reifier extends cls {
|
|
|
472
550
|
// adding to the trash list will skip reifying, and delete them
|
|
473
551
|
// if they are currently in the tree and otherwise untouched.
|
|
474
552
|
[_addOmitsToTrashList] () {
|
|
475
|
-
if (!this
|
|
553
|
+
if (!this.#omitDev && !this.#omitOptional && !this.#omitPeer) {
|
|
476
554
|
return
|
|
477
555
|
}
|
|
478
556
|
|
|
479
|
-
|
|
557
|
+
const timeEnd = time.start('reify:trashOmits')
|
|
480
558
|
|
|
481
559
|
for (const node of this.idealTree.inventory.values()) {
|
|
482
560
|
const { top } = node
|
|
@@ -494,26 +572,26 @@ module.exports = cls => class Reifier extends cls {
|
|
|
494
572
|
|
|
495
573
|
// omit node if the dep type matches any omit flags that were set
|
|
496
574
|
if (
|
|
497
|
-
node.peer && this
|
|
498
|
-
node.dev && this
|
|
499
|
-
node.optional && this
|
|
500
|
-
node.devOptional && this
|
|
575
|
+
node.peer && this.#omitPeer ||
|
|
576
|
+
node.dev && this.#omitDev ||
|
|
577
|
+
node.optional && this.#omitOptional ||
|
|
578
|
+
node.devOptional && this.#omitOptional && this.#omitDev
|
|
501
579
|
) {
|
|
502
580
|
this[_addNodeToTrashList](node)
|
|
503
581
|
}
|
|
504
582
|
}
|
|
505
583
|
|
|
506
|
-
|
|
584
|
+
timeEnd()
|
|
507
585
|
}
|
|
508
586
|
|
|
509
587
|
[_createSparseTree] () {
|
|
510
|
-
|
|
588
|
+
const timeEnd = time.start('reify:createSparse')
|
|
511
589
|
// if we call this fn again, we look for the previous list
|
|
512
590
|
// so that we can avoid making the same directory multiple times
|
|
513
591
|
const leaves = this.diff.leaves
|
|
514
592
|
.filter(diff => {
|
|
515
593
|
return (diff.action === 'ADD' || diff.action === 'CHANGE') &&
|
|
516
|
-
!this
|
|
594
|
+
!this.#sparseTreeDirs.has(diff.ideal.path) &&
|
|
517
595
|
!diff.ideal.isLink
|
|
518
596
|
})
|
|
519
597
|
.map(diff => diff.ideal)
|
|
@@ -530,37 +608,36 @@ module.exports = cls => class Reifier extends cls {
|
|
|
530
608
|
continue
|
|
531
609
|
}
|
|
532
610
|
dirsChecked.add(d)
|
|
533
|
-
const st = await lstat(d).catch(
|
|
611
|
+
const st = await lstat(d).catch(() => null)
|
|
534
612
|
// this can happen if we have a link to a package with a name
|
|
535
613
|
// that the filesystem treats as if it is the same thing.
|
|
536
614
|
// would be nice to have conditional istanbul ignores here...
|
|
537
615
|
/* istanbul ignore next - defense in depth */
|
|
538
616
|
if (st && !st.isDirectory()) {
|
|
539
617
|
const retired = retirePath(d)
|
|
540
|
-
this[
|
|
618
|
+
this.#retiredPaths[d] = retired
|
|
541
619
|
this[_trashList].add(retired)
|
|
542
620
|
await this[_renamePath](d, retired)
|
|
543
621
|
}
|
|
544
622
|
}
|
|
545
|
-
this
|
|
623
|
+
this.#sparseTreeDirs.add(node.path)
|
|
546
624
|
const made = await mkdir(node.path, { recursive: true })
|
|
547
625
|
// if the directory already exists, made will be undefined. if that's the case
|
|
548
626
|
// we don't want to remove it because we aren't the ones who created it so we
|
|
549
|
-
// omit it from the
|
|
627
|
+
// omit it from the #sparseTreeRoots
|
|
550
628
|
if (made) {
|
|
551
|
-
this
|
|
629
|
+
this.#sparseTreeRoots.add(made)
|
|
552
630
|
}
|
|
553
|
-
}))
|
|
554
|
-
.then(() => process.emit('timeEnd', 'reify:createSparse'))
|
|
631
|
+
})).then(timeEnd)
|
|
555
632
|
}
|
|
556
633
|
|
|
557
634
|
[_rollbackCreateSparseTree] (er) {
|
|
558
|
-
|
|
635
|
+
const timeEnd = time.start('reify:rollback:createSparse')
|
|
559
636
|
// cut the roots of the sparse tree that were created, not the leaves
|
|
560
|
-
const roots = this
|
|
637
|
+
const roots = this.#sparseTreeRoots
|
|
561
638
|
// also delete the moves that we retired, so that we can move them back
|
|
562
639
|
const failures = []
|
|
563
|
-
const targets = [...roots, ...Object.keys(this
|
|
640
|
+
const targets = [...roots, ...Object.keys(this.#retiredPaths)]
|
|
564
641
|
const unlinks = targets
|
|
565
642
|
.map(path => rm(path, { recursive: true, force: true }).catch(er => failures.push([path, er])))
|
|
566
643
|
return promiseAllRejectLate(unlinks).then(() => {
|
|
@@ -569,7 +646,7 @@ module.exports = cls => class Reifier extends cls {
|
|
|
569
646
|
log.warn('cleanup', 'Failed to remove some directories', failures)
|
|
570
647
|
}
|
|
571
648
|
})
|
|
572
|
-
.then(
|
|
649
|
+
.then(timeEnd)
|
|
573
650
|
.then(() => this[_rollbackRetireShallowNodes](er))
|
|
574
651
|
}
|
|
575
652
|
|
|
@@ -577,7 +654,7 @@ module.exports = cls => class Reifier extends cls {
|
|
|
577
654
|
// we need to unpack them, read that shrinkwrap file, and then update
|
|
578
655
|
// the tree by calling loadVirtual with the node as the root.
|
|
579
656
|
[_loadShrinkwrapsAndUpdateTrees] () {
|
|
580
|
-
const seen = this
|
|
657
|
+
const seen = this.#shrinkwrapInflated
|
|
581
658
|
const shrinkwraps = this.diff.leaves
|
|
582
659
|
.filter(d => (d.action === 'CHANGE' || d.action === 'ADD' || !d.action) &&
|
|
583
660
|
d.ideal.hasShrinkwrap && !seen.has(d.ideal) &&
|
|
@@ -587,7 +664,7 @@ module.exports = cls => class Reifier extends cls {
|
|
|
587
664
|
return
|
|
588
665
|
}
|
|
589
666
|
|
|
590
|
-
|
|
667
|
+
const timeEnd = time.start('reify:loadShrinkwraps')
|
|
591
668
|
|
|
592
669
|
const Arborist = this.constructor
|
|
593
670
|
return promiseAllRejectLate(shrinkwraps.map(diff => {
|
|
@@ -604,7 +681,7 @@ module.exports = cls => class Reifier extends cls {
|
|
|
604
681
|
.then(() => this[_createSparseTree]())
|
|
605
682
|
.then(() => this[_addOmitsToTrashList]())
|
|
606
683
|
.then(() => this[_loadShrinkwrapsAndUpdateTrees]())
|
|
607
|
-
.then(
|
|
684
|
+
.then(timeEnd)
|
|
608
685
|
}
|
|
609
686
|
|
|
610
687
|
// create a symlink for Links, extract for Nodes
|
|
@@ -619,8 +696,7 @@ module.exports = cls => class Reifier extends cls {
|
|
|
619
696
|
return node
|
|
620
697
|
}
|
|
621
698
|
|
|
622
|
-
const
|
|
623
|
-
process.emit('time', timer)
|
|
699
|
+
const timeEnd = time.start(`reifyNode:${node.location}`)
|
|
624
700
|
this.addTracker('reify', node.name, node.location)
|
|
625
701
|
|
|
626
702
|
const { npmVersion, nodeVersion, cpu, os, libc } = this.options
|
|
@@ -636,35 +712,40 @@ module.exports = cls => class Reifier extends cls {
|
|
|
636
712
|
checkPlatform(node.package, false, { cpu, os, libc })
|
|
637
713
|
}
|
|
638
714
|
await this[_checkBins](node)
|
|
639
|
-
await this
|
|
640
|
-
|
|
715
|
+
await this.#extractOrLink(node)
|
|
716
|
+
const { _id, deprecated } = node.package
|
|
717
|
+
// The .catch is in _handleOptionalFailure. Not ideal, this should be cleaned up.
|
|
718
|
+
// eslint-disable-next-line promise/always-return
|
|
719
|
+
if (deprecated) {
|
|
720
|
+
log.warn('deprecated', `${_id}: ${deprecated}`)
|
|
721
|
+
}
|
|
641
722
|
})
|
|
642
723
|
|
|
643
724
|
return this[_handleOptionalFailure](node, p)
|
|
644
725
|
.then(() => {
|
|
645
726
|
this.finishTracker('reify', node.name, node.location)
|
|
646
|
-
|
|
727
|
+
timeEnd()
|
|
647
728
|
return node
|
|
648
729
|
})
|
|
649
730
|
}
|
|
650
731
|
|
|
651
732
|
// do not allow node_modules to be a symlink
|
|
652
|
-
async
|
|
653
|
-
if (this.options.force || this
|
|
733
|
+
async #validateNodeModules (nm) {
|
|
734
|
+
if (this.options.force || this.#nmValidated.has(nm)) {
|
|
654
735
|
return
|
|
655
736
|
}
|
|
656
737
|
const st = await lstat(nm).catch(() => null)
|
|
657
738
|
if (!st || st.isDirectory()) {
|
|
658
|
-
this
|
|
739
|
+
this.#nmValidated.add(nm)
|
|
659
740
|
return
|
|
660
741
|
}
|
|
661
742
|
log.warn('reify', 'Removing non-directory', nm)
|
|
662
743
|
await rm(nm, { recursive: true, force: true })
|
|
663
744
|
}
|
|
664
745
|
|
|
665
|
-
async
|
|
746
|
+
async #extractOrLink (node) {
|
|
666
747
|
const nm = resolve(node.parent.path, 'node_modules')
|
|
667
|
-
await this
|
|
748
|
+
await this.#validateNodeModules(nm)
|
|
668
749
|
|
|
669
750
|
if (!node.isLink) {
|
|
670
751
|
// in normal cases, node.resolved should *always* be set by now.
|
|
@@ -676,7 +757,7 @@ module.exports = cls => class Reifier extends cls {
|
|
|
676
757
|
// entirely, since we can't possibly reify it.
|
|
677
758
|
let res = null
|
|
678
759
|
if (node.resolved) {
|
|
679
|
-
const registryResolved = this
|
|
760
|
+
const registryResolved = this.#registryResolved(node.resolved)
|
|
680
761
|
if (registryResolved) {
|
|
681
762
|
res = `${node.name}@${registryResolved}`
|
|
682
763
|
}
|
|
@@ -698,7 +779,7 @@ module.exports = cls => class Reifier extends cls {
|
|
|
698
779
|
return
|
|
699
780
|
}
|
|
700
781
|
await debug(async () => {
|
|
701
|
-
const st = await lstat(node.path).catch(
|
|
782
|
+
const st = await lstat(node.path).catch(() => null)
|
|
702
783
|
if (st && !st.isDirectory()) {
|
|
703
784
|
debug.log('unpacking into a non-directory', node)
|
|
704
785
|
throw Object.assign(new Error('ENOTDIR: not a directory'), {
|
|
@@ -722,10 +803,8 @@ module.exports = cls => class Reifier extends cls {
|
|
|
722
803
|
|
|
723
804
|
// node.isLink
|
|
724
805
|
await rm(node.path, { recursive: true, force: true })
|
|
725
|
-
await this[_symlink](node)
|
|
726
|
-
}
|
|
727
806
|
|
|
728
|
-
|
|
807
|
+
// symlink
|
|
729
808
|
const dir = dirname(node.path)
|
|
730
809
|
const target = node.realpath
|
|
731
810
|
const rel = relative(dir, target)
|
|
@@ -733,17 +812,10 @@ module.exports = cls => class Reifier extends cls {
|
|
|
733
812
|
return symlink(rel, node.path, 'junction')
|
|
734
813
|
}
|
|
735
814
|
|
|
736
|
-
[_warnDeprecated] (node) {
|
|
737
|
-
const { _id, deprecated } = node.package
|
|
738
|
-
if (deprecated) {
|
|
739
|
-
log.warn('deprecated', `${_id}: ${deprecated}`)
|
|
740
|
-
}
|
|
741
|
-
}
|
|
742
|
-
|
|
743
815
|
// if the node is optional, then the failure of the promise is nonfatal
|
|
744
816
|
// just add it and its optional set to the trash list.
|
|
745
817
|
[_handleOptionalFailure] (node, p) {
|
|
746
|
-
return (node.optional ? p.catch(
|
|
818
|
+
return (node.optional ? p.catch(() => {
|
|
747
819
|
const set = optionalSet(node)
|
|
748
820
|
for (node of set) {
|
|
749
821
|
log.verbose('reify', 'failed optional dependency', node.path)
|
|
@@ -752,7 +824,7 @@ module.exports = cls => class Reifier extends cls {
|
|
|
752
824
|
}) : p).then(() => node)
|
|
753
825
|
}
|
|
754
826
|
|
|
755
|
-
|
|
827
|
+
#registryResolved (resolved) {
|
|
756
828
|
// the default registry url is a magic value meaning "the currently
|
|
757
829
|
// configured registry".
|
|
758
830
|
// `resolved` must never be falsey.
|
|
@@ -782,21 +854,51 @@ module.exports = cls => class Reifier extends cls {
|
|
|
782
854
|
// by the contents of the package. however, in their case, rather than
|
|
783
855
|
// shipping a virtual tree that must be reified, they ship an entire
|
|
784
856
|
// reified actual tree that must be unpacked and not modified.
|
|
785
|
-
[_loadBundlesAndUpdateTrees] (
|
|
786
|
-
|
|
787
|
-
|
|
857
|
+
[_loadBundlesAndUpdateTrees] (depth = 0, bundlesByDepth) {
|
|
858
|
+
let maxBundleDepth
|
|
859
|
+
if (!bundlesByDepth) {
|
|
860
|
+
bundlesByDepth = new Map()
|
|
861
|
+
maxBundleDepth = -1
|
|
862
|
+
dfwalk({
|
|
863
|
+
tree: this.diff,
|
|
864
|
+
visit: diff => {
|
|
865
|
+
const node = diff.ideal
|
|
866
|
+
if (!node) {
|
|
867
|
+
return
|
|
868
|
+
}
|
|
869
|
+
if (node.isProjectRoot) {
|
|
870
|
+
return
|
|
871
|
+
}
|
|
872
|
+
|
|
873
|
+
const { bundleDependencies } = node.package
|
|
874
|
+
if (bundleDependencies && bundleDependencies.length) {
|
|
875
|
+
maxBundleDepth = Math.max(maxBundleDepth, node.depth)
|
|
876
|
+
if (!bundlesByDepth.has(node.depth)) {
|
|
877
|
+
bundlesByDepth.set(node.depth, [node])
|
|
878
|
+
} else {
|
|
879
|
+
bundlesByDepth.get(node.depth).push(node)
|
|
880
|
+
}
|
|
881
|
+
}
|
|
882
|
+
},
|
|
883
|
+
getChildren: diff => diff.children,
|
|
884
|
+
})
|
|
885
|
+
|
|
886
|
+
bundlesByDepth.set('maxBundleDepth', maxBundleDepth)
|
|
887
|
+
} else {
|
|
888
|
+
maxBundleDepth = bundlesByDepth.get('maxBundleDepth')
|
|
889
|
+
}
|
|
890
|
+
|
|
788
891
|
if (depth === 0) {
|
|
789
|
-
|
|
892
|
+
time.start('reify:loadBundles')
|
|
790
893
|
}
|
|
791
894
|
|
|
792
|
-
const maxBundleDepth = bundlesByDepth.get('maxBundleDepth')
|
|
793
895
|
if (depth > maxBundleDepth) {
|
|
794
896
|
// if we did something, then prune the tree and update the diffs
|
|
795
897
|
if (maxBundleDepth !== -1) {
|
|
796
|
-
this
|
|
898
|
+
this.#pruneBundledMetadeps(bundlesByDepth)
|
|
797
899
|
this[_diffTrees]()
|
|
798
900
|
}
|
|
799
|
-
|
|
901
|
+
time.end('reify:loadBundles')
|
|
800
902
|
return
|
|
801
903
|
}
|
|
802
904
|
|
|
@@ -814,7 +916,7 @@ module.exports = cls => class Reifier extends cls {
|
|
|
814
916
|
// extract all the nodes with bundles
|
|
815
917
|
return promiseCallLimit(set.map(node => {
|
|
816
918
|
return () => {
|
|
817
|
-
this
|
|
919
|
+
this.#bundleUnpacked.add(node)
|
|
818
920
|
return this[_reifyNode](node)
|
|
819
921
|
}
|
|
820
922
|
}), { rejectLate: true })
|
|
@@ -843,46 +945,15 @@ module.exports = cls => class Reifier extends cls {
|
|
|
843
945
|
},
|
|
844
946
|
})
|
|
845
947
|
for (const name of notTransplanted) {
|
|
846
|
-
this
|
|
948
|
+
this.#bundleMissing.add(node.children.get(name))
|
|
847
949
|
}
|
|
848
950
|
})))
|
|
849
951
|
// move onto the next level of bundled items
|
|
850
952
|
.then(() => this[_loadBundlesAndUpdateTrees](depth + 1, bundlesByDepth))
|
|
851
953
|
}
|
|
852
954
|
|
|
853
|
-
[_getBundlesByDepth] () {
|
|
854
|
-
const bundlesByDepth = new Map()
|
|
855
|
-
let maxBundleDepth = -1
|
|
856
|
-
dfwalk({
|
|
857
|
-
tree: this.diff,
|
|
858
|
-
visit: diff => {
|
|
859
|
-
const node = diff.ideal
|
|
860
|
-
if (!node) {
|
|
861
|
-
return
|
|
862
|
-
}
|
|
863
|
-
if (node.isProjectRoot) {
|
|
864
|
-
return
|
|
865
|
-
}
|
|
866
|
-
|
|
867
|
-
const { bundleDependencies } = node.package
|
|
868
|
-
if (bundleDependencies && bundleDependencies.length) {
|
|
869
|
-
maxBundleDepth = Math.max(maxBundleDepth, node.depth)
|
|
870
|
-
if (!bundlesByDepth.has(node.depth)) {
|
|
871
|
-
bundlesByDepth.set(node.depth, [node])
|
|
872
|
-
} else {
|
|
873
|
-
bundlesByDepth.get(node.depth).push(node)
|
|
874
|
-
}
|
|
875
|
-
}
|
|
876
|
-
},
|
|
877
|
-
getChildren: diff => diff.children,
|
|
878
|
-
})
|
|
879
|
-
|
|
880
|
-
bundlesByDepth.set('maxBundleDepth', maxBundleDepth)
|
|
881
|
-
return bundlesByDepth
|
|
882
|
-
}
|
|
883
|
-
|
|
884
955
|
// https://github.com/npm/cli/issues/1597#issuecomment-667639545
|
|
885
|
-
|
|
956
|
+
#pruneBundledMetadeps (bundlesByDepth) {
|
|
886
957
|
const bundleShadowed = new Set()
|
|
887
958
|
|
|
888
959
|
// Example dep graph:
|
|
@@ -981,7 +1052,7 @@ module.exports = cls => class Reifier extends cls {
|
|
|
981
1052
|
// before finishing the reify() and returning the tree. Thus, we do
|
|
982
1053
|
// NOT return the promise, as the intent is for this to run in parallel
|
|
983
1054
|
// with the reification, and be resolved at a later time.
|
|
984
|
-
|
|
1055
|
+
const timeEnd = time.start('reify:audit')
|
|
985
1056
|
const options = { ...this.options }
|
|
986
1057
|
const tree = this.idealTree
|
|
987
1058
|
|
|
@@ -995,7 +1066,7 @@ module.exports = cls => class Reifier extends cls {
|
|
|
995
1066
|
}
|
|
996
1067
|
|
|
997
1068
|
this.auditReport = AuditReport.load(tree, options).then(res => {
|
|
998
|
-
|
|
1069
|
+
timeEnd()
|
|
999
1070
|
return res
|
|
1000
1071
|
})
|
|
1001
1072
|
}
|
|
@@ -1005,7 +1076,7 @@ module.exports = cls => class Reifier extends cls {
|
|
|
1005
1076
|
// kicking off each unpack job. If any fail, we rm the sparse
|
|
1006
1077
|
// tree entirely and try to put everything back where it was.
|
|
1007
1078
|
[_unpackNewModules] () {
|
|
1008
|
-
|
|
1079
|
+
const timeEnd = time.start('reify:unpack')
|
|
1009
1080
|
const unpacks = []
|
|
1010
1081
|
dfwalk({
|
|
1011
1082
|
tree: this.diff,
|
|
@@ -1016,9 +1087,9 @@ module.exports = cls => class Reifier extends cls {
|
|
|
1016
1087
|
}
|
|
1017
1088
|
|
|
1018
1089
|
const node = diff.ideal
|
|
1019
|
-
const bd = this
|
|
1020
|
-
const sw = this
|
|
1021
|
-
const bundleMissing = this
|
|
1090
|
+
const bd = this.#bundleUnpacked.has(node)
|
|
1091
|
+
const sw = this.#shrinkwrapInflated.has(node)
|
|
1092
|
+
const bundleMissing = this.#bundleMissing.has(node)
|
|
1022
1093
|
|
|
1023
1094
|
// check whether we still need to unpack this one.
|
|
1024
1095
|
// test the inDepBundle last, since that's potentially a tree walk.
|
|
@@ -1038,8 +1109,7 @@ module.exports = cls => class Reifier extends cls {
|
|
|
1038
1109
|
},
|
|
1039
1110
|
getChildren: diff => diff.children,
|
|
1040
1111
|
})
|
|
1041
|
-
return promiseAllRejectLate(unpacks)
|
|
1042
|
-
.then(() => process.emit('timeEnd', 'reify:unpack'))
|
|
1112
|
+
return promiseAllRejectLate(unpacks).then(timeEnd)
|
|
1043
1113
|
}
|
|
1044
1114
|
|
|
1045
1115
|
// This is the part where we move back the unchanging nodes that were
|
|
@@ -1054,9 +1124,9 @@ module.exports = cls => class Reifier extends cls {
|
|
|
1054
1124
|
// This is sort of an inverse diff tree, of all the nodes where
|
|
1055
1125
|
// the actualTree and idealTree _don't_ differ, starting from the
|
|
1056
1126
|
// shallowest nodes that we moved aside in the first place.
|
|
1057
|
-
|
|
1058
|
-
const moves = this
|
|
1059
|
-
this
|
|
1127
|
+
const timeEnd = time.start('reify:unretire')
|
|
1128
|
+
const moves = this.#retiredPaths
|
|
1129
|
+
this.#retiredUnchanged = {}
|
|
1060
1130
|
return promiseAllRejectLate(this.diff.children.map(diff => {
|
|
1061
1131
|
// skip if nothing was retired
|
|
1062
1132
|
if (diff.action !== 'CHANGE' && diff.action !== 'REMOVE') {
|
|
@@ -1079,7 +1149,7 @@ module.exports = cls => class Reifier extends cls {
|
|
|
1079
1149
|
}
|
|
1080
1150
|
})
|
|
1081
1151
|
|
|
1082
|
-
this[
|
|
1152
|
+
this.#retiredUnchanged[retireFolder] = []
|
|
1083
1153
|
return promiseAllRejectLate(diff.unchanged.map(node => {
|
|
1084
1154
|
// no need to roll back links, since we'll just delete them anyway
|
|
1085
1155
|
if (node.isLink) {
|
|
@@ -1088,11 +1158,11 @@ module.exports = cls => class Reifier extends cls {
|
|
|
1088
1158
|
}
|
|
1089
1159
|
|
|
1090
1160
|
// will have been moved/unpacked along with bundler
|
|
1091
|
-
if (node.inDepBundle && !this
|
|
1161
|
+
if (node.inDepBundle && !this.#bundleMissing.has(node)) {
|
|
1092
1162
|
return
|
|
1093
1163
|
}
|
|
1094
1164
|
|
|
1095
|
-
this[
|
|
1165
|
+
this.#retiredUnchanged[retireFolder].push(node)
|
|
1096
1166
|
|
|
1097
1167
|
const rel = relative(realFolder, node.path)
|
|
1098
1168
|
const fromPath = resolve(retireFolder, rel)
|
|
@@ -1102,8 +1172,7 @@ module.exports = cls => class Reifier extends cls {
|
|
|
1102
1172
|
const dir = bd && bd.length ? node.path + '/node_modules' : node.path
|
|
1103
1173
|
return mkdir(dir, { recursive: true }).then(() => this[_moveContents](node, fromPath))
|
|
1104
1174
|
}))
|
|
1105
|
-
}))
|
|
1106
|
-
.then(() => process.emit('timeEnd', 'reify:unretire'))
|
|
1175
|
+
})).then(timeEnd)
|
|
1107
1176
|
}
|
|
1108
1177
|
|
|
1109
1178
|
// move the contents from the fromPath to the node.path
|
|
@@ -1120,10 +1189,10 @@ module.exports = cls => class Reifier extends cls {
|
|
|
1120
1189
|
}
|
|
1121
1190
|
|
|
1122
1191
|
[_rollbackMoveBackRetiredUnchanged] (er) {
|
|
1123
|
-
const moves = this
|
|
1192
|
+
const moves = this.#retiredPaths
|
|
1124
1193
|
// flip the mapping around to go back
|
|
1125
1194
|
const realFolders = new Map(Object.entries(moves).map(([k, v]) => [v, k]))
|
|
1126
|
-
const promises = Object.entries(this
|
|
1195
|
+
const promises = Object.entries(this.#retiredUnchanged)
|
|
1127
1196
|
.map(([retireFolder, nodes]) => promiseAllRejectLate(nodes.map(node => {
|
|
1128
1197
|
const realFolder = realFolders.get(retireFolder)
|
|
1129
1198
|
const rel = relative(realFolder, node.path)
|
|
@@ -1135,7 +1204,7 @@ module.exports = cls => class Reifier extends cls {
|
|
|
1135
1204
|
}
|
|
1136
1205
|
|
|
1137
1206
|
[_build] () {
|
|
1138
|
-
|
|
1207
|
+
const timeEnd = time.start('reify:build')
|
|
1139
1208
|
|
|
1140
1209
|
// for all the things being installed, run their appropriate scripts
|
|
1141
1210
|
// run in tip->root order, so as to be more likely to build a node's
|
|
@@ -1167,8 +1236,7 @@ module.exports = cls => class Reifier extends cls {
|
|
|
1167
1236
|
}
|
|
1168
1237
|
}
|
|
1169
1238
|
|
|
1170
|
-
return this.rebuild({ nodes, handleOptionalFailure: true })
|
|
1171
|
-
.then(() => process.emit('timeEnd', 'reify:build'))
|
|
1239
|
+
return this.rebuild({ nodes, handleOptionalFailure: true }).then(timeEnd)
|
|
1172
1240
|
}
|
|
1173
1241
|
|
|
1174
1242
|
// the tree is pretty much built now, so it's cleanup time.
|
|
@@ -1176,7 +1244,7 @@ module.exports = cls => class Reifier extends cls {
|
|
|
1176
1244
|
// If this fails, there isn't much we can do but tell the user about it.
|
|
1177
1245
|
// Thankfully, it's pretty unlikely that it'll fail, since rm is a node builtin.
|
|
1178
1246
|
async [_removeTrash] () {
|
|
1179
|
-
|
|
1247
|
+
const timeEnd = time.start('reify:trash')
|
|
1180
1248
|
const promises = []
|
|
1181
1249
|
const failures = []
|
|
1182
1250
|
const _rm = path => rm(path, { recursive: true, force: true }).catch(er => failures.push([path, er]))
|
|
@@ -1189,7 +1257,8 @@ module.exports = cls => class Reifier extends cls {
|
|
|
1189
1257
|
if (failures.length) {
|
|
1190
1258
|
log.warn('cleanup', 'Failed to remove some directories', failures)
|
|
1191
1259
|
}
|
|
1192
|
-
|
|
1260
|
+
|
|
1261
|
+
timeEnd()
|
|
1193
1262
|
}
|
|
1194
1263
|
|
|
1195
1264
|
// last but not least, we save the ideal tree metadata to the package-lock
|
|
@@ -1215,14 +1284,14 @@ module.exports = cls => class Reifier extends cls {
|
|
|
1215
1284
|
const saveIdealTree = !(
|
|
1216
1285
|
(!save && !hasUpdates)
|
|
1217
1286
|
|| this.options.global
|
|
1218
|
-
|| this
|
|
1287
|
+
|| this.options.dryRun
|
|
1219
1288
|
)
|
|
1220
1289
|
|
|
1221
1290
|
if (!saveIdealTree) {
|
|
1222
1291
|
return false
|
|
1223
1292
|
}
|
|
1224
1293
|
|
|
1225
|
-
|
|
1294
|
+
const timeEnd = time.start('reify:save')
|
|
1226
1295
|
|
|
1227
1296
|
const updatedTrees = new Set()
|
|
1228
1297
|
const updateNodes = nodes => {
|
|
@@ -1251,7 +1320,7 @@ module.exports = cls => class Reifier extends cls {
|
|
|
1251
1320
|
const isLocalDep = req.type === 'directory' || req.type === 'file'
|
|
1252
1321
|
if (req.registry) {
|
|
1253
1322
|
const version = child.version
|
|
1254
|
-
const prefixRange = version ? this
|
|
1323
|
+
const prefixRange = version ? this.options.savePrefix + version : '*'
|
|
1255
1324
|
// if we installed a range, then we save the range specified
|
|
1256
1325
|
// if it is not a subset of the ^x.y.z. eg, installing a range
|
|
1257
1326
|
// of `1.x <1.2.3` will not be saved as `^1.2.0`, because that
|
|
@@ -1286,7 +1355,7 @@ module.exports = cls => class Reifier extends cls {
|
|
|
1286
1355
|
// using their relative path
|
|
1287
1356
|
if (edge.type === 'workspace') {
|
|
1288
1357
|
const { version } = edge.to.target
|
|
1289
|
-
const prefixRange = version ? this
|
|
1358
|
+
const prefixRange = version ? this.options.savePrefix + version : '*'
|
|
1290
1359
|
newSpec = prefixRange
|
|
1291
1360
|
} else {
|
|
1292
1361
|
// save the relative path in package.json
|
|
@@ -1455,154 +1524,12 @@ module.exports = cls => class Reifier extends cls {
|
|
|
1455
1524
|
|
|
1456
1525
|
// TODO this ignores options.save
|
|
1457
1526
|
await this.idealTree.meta.save({
|
|
1458
|
-
format: (this
|
|
1459
|
-
: this
|
|
1527
|
+
format: (this.options.formatPackageLock && format) ? format
|
|
1528
|
+
: this.options.formatPackageLock,
|
|
1460
1529
|
})
|
|
1461
1530
|
}
|
|
1462
1531
|
|
|
1463
|
-
|
|
1532
|
+
timeEnd()
|
|
1464
1533
|
return true
|
|
1465
1534
|
}
|
|
1466
|
-
|
|
1467
|
-
async [_copyIdealToActual] () {
|
|
1468
|
-
// clean up any trash that is still in the tree
|
|
1469
|
-
for (const path of this[_trashList]) {
|
|
1470
|
-
const loc = relpath(this.idealTree.realpath, path)
|
|
1471
|
-
const node = this.idealTree.inventory.get(loc)
|
|
1472
|
-
if (node && node.root === this.idealTree) {
|
|
1473
|
-
node.parent = null
|
|
1474
|
-
}
|
|
1475
|
-
}
|
|
1476
|
-
|
|
1477
|
-
// if we filtered to only certain nodes, then anything ELSE needs
|
|
1478
|
-
// to be untouched in the resulting actual tree, even if it differs
|
|
1479
|
-
// in the idealTree. Copy over anything that was in the actual and
|
|
1480
|
-
// was not changed, delete anything in the ideal and not actual.
|
|
1481
|
-
// Then we move the entire idealTree over to this.actualTree, and
|
|
1482
|
-
// save the hidden lockfile.
|
|
1483
|
-
if (this.diff && this.diff.filterSet.size) {
|
|
1484
|
-
const reroot = new Set()
|
|
1485
|
-
|
|
1486
|
-
const { filterSet } = this.diff
|
|
1487
|
-
const seen = new Set()
|
|
1488
|
-
for (const [loc, ideal] of this.idealTree.inventory.entries()) {
|
|
1489
|
-
seen.add(loc)
|
|
1490
|
-
|
|
1491
|
-
// if it's an ideal node from the filter set, then skip it
|
|
1492
|
-
// because we already made whatever changes were necessary
|
|
1493
|
-
if (filterSet.has(ideal)) {
|
|
1494
|
-
continue
|
|
1495
|
-
}
|
|
1496
|
-
|
|
1497
|
-
// otherwise, if it's not in the actualTree, then it's not a thing
|
|
1498
|
-
// that we actually added. And if it IS in the actualTree, then
|
|
1499
|
-
// it's something that we left untouched, so we need to record
|
|
1500
|
-
// that.
|
|
1501
|
-
const actual = this.actualTree.inventory.get(loc)
|
|
1502
|
-
if (!actual) {
|
|
1503
|
-
ideal.root = null
|
|
1504
|
-
} else {
|
|
1505
|
-
if ([...actual.linksIn].some(link => filterSet.has(link))) {
|
|
1506
|
-
seen.add(actual.location)
|
|
1507
|
-
continue
|
|
1508
|
-
}
|
|
1509
|
-
const { realpath, isLink } = actual
|
|
1510
|
-
if (isLink && ideal.isLink && ideal.realpath === realpath) {
|
|
1511
|
-
continue
|
|
1512
|
-
} else {
|
|
1513
|
-
reroot.add(actual)
|
|
1514
|
-
}
|
|
1515
|
-
}
|
|
1516
|
-
}
|
|
1517
|
-
|
|
1518
|
-
// now find any actual nodes that may not be present in the ideal
|
|
1519
|
-
// tree, but were left behind by virtue of not being in the filter
|
|
1520
|
-
for (const [loc, actual] of this.actualTree.inventory.entries()) {
|
|
1521
|
-
if (seen.has(loc)) {
|
|
1522
|
-
continue
|
|
1523
|
-
}
|
|
1524
|
-
seen.add(loc)
|
|
1525
|
-
|
|
1526
|
-
// we know that this is something that ISN'T in the idealTree,
|
|
1527
|
-
// or else we will have addressed it in the previous loop.
|
|
1528
|
-
// If it's in the filterSet, that means we intentionally removed
|
|
1529
|
-
// it, so nothing to do here.
|
|
1530
|
-
if (filterSet.has(actual)) {
|
|
1531
|
-
continue
|
|
1532
|
-
}
|
|
1533
|
-
|
|
1534
|
-
reroot.add(actual)
|
|
1535
|
-
}
|
|
1536
|
-
|
|
1537
|
-
// go through the rerooted actual nodes, and move them over.
|
|
1538
|
-
for (const actual of reroot) {
|
|
1539
|
-
actual.root = this.idealTree
|
|
1540
|
-
}
|
|
1541
|
-
|
|
1542
|
-
// prune out any tops that lack a linkIn, they are no longer relevant.
|
|
1543
|
-
for (const top of this.idealTree.tops) {
|
|
1544
|
-
if (top.linksIn.size === 0) {
|
|
1545
|
-
top.root = null
|
|
1546
|
-
}
|
|
1547
|
-
}
|
|
1548
|
-
|
|
1549
|
-
// need to calculate dep flags, since nodes may have been marked
|
|
1550
|
-
// as extraneous or otherwise incorrect during transit.
|
|
1551
|
-
calcDepFlags(this.idealTree)
|
|
1552
|
-
}
|
|
1553
|
-
|
|
1554
|
-
// save the ideal's meta as a hidden lockfile after we actualize it
|
|
1555
|
-
this.idealTree.meta.filename =
|
|
1556
|
-
this.idealTree.realpath + '/node_modules/.package-lock.json'
|
|
1557
|
-
this.idealTree.meta.hiddenLockfile = true
|
|
1558
|
-
this.idealTree.meta.lockfileVersion = defaultLockfileVersion
|
|
1559
|
-
|
|
1560
|
-
this.actualTree = this.idealTree
|
|
1561
|
-
this.idealTree = null
|
|
1562
|
-
|
|
1563
|
-
if (!this.options.global) {
|
|
1564
|
-
await this.actualTree.meta.save()
|
|
1565
|
-
const ignoreScripts = !!this.options.ignoreScripts
|
|
1566
|
-
// if we aren't doing a dry run or ignoring scripts and we actually made changes to the dep
|
|
1567
|
-
// tree, then run the dependencies scripts
|
|
1568
|
-
if (!this[_dryRun] && !ignoreScripts && this.diff && this.diff.children.length) {
|
|
1569
|
-
const { path, package: pkg } = this.actualTree.target
|
|
1570
|
-
const stdio = this.options.foregroundScripts ? 'inherit' : 'pipe'
|
|
1571
|
-
const { scripts = {} } = pkg
|
|
1572
|
-
for (const event of ['predependencies', 'dependencies', 'postdependencies']) {
|
|
1573
|
-
if (Object.prototype.hasOwnProperty.call(scripts, event)) {
|
|
1574
|
-
const timer = `reify:run:${event}`
|
|
1575
|
-
process.emit('time', timer)
|
|
1576
|
-
log.info('run', pkg._id, event, scripts[event])
|
|
1577
|
-
await runScript({
|
|
1578
|
-
event,
|
|
1579
|
-
path,
|
|
1580
|
-
pkg,
|
|
1581
|
-
stdio,
|
|
1582
|
-
scriptShell: this.options.scriptShell,
|
|
1583
|
-
})
|
|
1584
|
-
process.emit('timeEnd', timer)
|
|
1585
|
-
}
|
|
1586
|
-
}
|
|
1587
|
-
}
|
|
1588
|
-
}
|
|
1589
|
-
}
|
|
1590
|
-
|
|
1591
|
-
async dedupe (options = {}) {
|
|
1592
|
-
// allow the user to set options on the ctor as well.
|
|
1593
|
-
// XXX: deprecate separate method options objects.
|
|
1594
|
-
options = { ...this.options, ...options }
|
|
1595
|
-
const tree = await this.loadVirtual().catch(() => this.loadActual())
|
|
1596
|
-
const names = []
|
|
1597
|
-
for (const name of tree.inventory.query('name')) {
|
|
1598
|
-
if (tree.inventory.query('name', name).size > 1) {
|
|
1599
|
-
names.push(name)
|
|
1600
|
-
}
|
|
1601
|
-
}
|
|
1602
|
-
return this.reify({
|
|
1603
|
-
...options,
|
|
1604
|
-
preferDedupe: true,
|
|
1605
|
-
update: { names },
|
|
1606
|
-
})
|
|
1607
|
-
}
|
|
1608
1535
|
}
|