|
38 | 38 | const Shrinkwrap = require('../shrinkwrap.js')
|
39 | 39 | const { defaultLockfileVersion } = Shrinkwrap
|
40 | 40 |
|
41 |
| -const _retiredPaths = Symbol('retiredPaths') |
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 _getBundlesByDepth = Symbol('getBundlesByDepth') |
48 |
| -const _registryResolved = Symbol('registryResolved') |
49 |
| -const _addNodeToTrashList = Symbol.for('addNodeToTrashList') |
| 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 |
| -const _omitDev = Symbol('omitDev') |
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 |
| -const _formatPackageLock = Symbol.for('formatPackageLock') |
| 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[_packageLockOnly] && this.options.global) { |
| 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[_omitDev] = omit.has('dev') |
147 |
| - this[_omitOptional] = omit.has('optional') |
148 |
| - this[_omitPeer] = omit.has('peer') |
| 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 | 120 | const timeEnd = time.start('reify')
|
153 |
| - await this[_validatePath]() |
| 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
|
|
169 | 146 | this.idealTree = oldTree
|
170 | 147 | }
|
171 | 148 | await this[_saveIdealTree](options)
|
172 |
| - await this[_copyIdealToActual]() |
| 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 | + } |
| 157 | + |
| 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() |
| 166 | + |
| 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) |
| 233 | + } |
| 234 | + |
| 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 | + } |
173 | 267 | // This is a very bad pattern and I can't wait to stop doing it
|
174 | 268 | this.auditReport = await this.auditReport
|
175 | 269 |
|
|
178 | 272 | return treeCheck(this.actualTree)
|
179 | 273 | }
|
180 | 274 |
|
181 |
| - async [_validatePath] () { |
182 |
| - // don't create missing dirs on dry runs |
183 |
| - if (this[_packageLockOnly] || this[_dryRun]) { |
184 |
| - return |
185 |
| - } |
186 |
| - |
187 |
| - // we do NOT want to set ownership on this folder, especially |
188 |
| - // recursively, because it can have other side effects to do that |
189 |
| - // in a project directory. We just want to make it if it's missing. |
190 |
| - await mkdir(resolve(this.path), { recursive: true }) |
191 |
| - |
192 |
| - // do not allow the top-level node_modules to be a symlink |
193 |
| - await this[_validateNodeModules](resolve(this.path, 'node_modules')) |
194 |
| - } |
195 |
| - |
196 | 275 | async [_reifyPackages] () {
|
197 | 276 | // we don't submit the audit report or write to disk on dry runs
|
198 |
| - if (this[_dryRun]) { |
| 277 | + if (this.options.dryRun) { |
199 | 278 | return
|
200 | 279 | }
|
201 | 280 |
|
202 |
| - if (this[_packageLockOnly]) { |
| 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 | 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
|
|
272 | 352 | const timeEnd = time.start('reify:loadTrees')
|
273 | 353 | const bitOpt = {
|
274 | 354 | ...options,
|
275 |
| - complete: this[_packageLockOnly] || this[_dryRun], |
| 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[_packageLockOnly]) { |
| 359 | + if (this.options.packageLockOnly) { |
280 | 360 | return this.buildIdealTree(bitOpt).then(timeEnd)
|
281 | 361 | }
|
282 | 362 |
|
|
325 | 405 | }
|
326 | 406 |
|
327 | 407 | [_diffTrees] () {
|
328 |
| - if (this[_packageLockOnly]) { |
| 408 | + if (this.options.packageLockOnly) { |
329 | 409 | return
|
330 | 410 | }
|
331 | 411 |
|
|
383 | 463 | // find all the nodes that need to change between the actual
|
384 | 464 | // and ideal trees.
|
385 | 465 | this.diff = Diff.calculate({
|
386 |
| - shrinkwrapInflated: this[_shrinkwrapInflated], |
| 466 | + shrinkwrapInflated: this.#shrinkwrapInflated, |
387 | 467 | filterNodes,
|
388 | 468 | actual: this.actualTree,
|
389 | 469 | ideal: this.idealTree,
|
|
405 | 485 | // replace them when rolling back on failure.
|
406 | 486 | [_addNodeToTrashList] (node, retire = false) {
|
407 | 487 | const paths = [node.path, ...node.binPaths]
|
408 |
| - const moves = this[_retiredPaths] |
| 488 | + const moves = this.#retiredPaths |
409 | 489 | log.silly('reify', 'mark', retire ? 'retired' : 'deleted', paths)
|
410 | 490 | for (const path of paths) {
|
411 | 491 | if (retire) {
|
|
422 | 502 | // changed or removed, so that we can rollback if necessary.
|
423 | 503 | [_retireShallowNodes] () {
|
424 | 504 | const timeEnd = time.start('reify:retireShallow')
|
425 |
| - const moves = this[_retiredPaths] = {} |
| 505 | + const moves = this.#retiredPaths = {} |
426 | 506 | for (const diff of this.diff.children) {
|
427 | 507 | if (diff.action === 'CHANGE' || diff.action === 'REMOVE') {
|
428 | 508 | // we'll have to clean these up at the end, so add them to the list
|
|
455 | 535 |
|
456 | 536 | [_rollbackRetireShallowNodes] (er) {
|
457 | 537 | const timeEnd = time.start('reify:rollback:retireShallow')
|
458 |
| - const moves = this[_retiredPaths] |
| 538 | + const moves = this.#retiredPaths |
459 | 539 | const movePromises = Object.entries(moves)
|
460 | 540 | .map(([from, to]) => this[_renamePath](to, from))
|
461 | 541 | return promiseAllRejectLate(movePromises)
|
462 | 542 | // ignore subsequent rollback errors
|
463 | 543 | .catch(er => {})
|
464 | 544 | .then(timeEnd)
|
465 | 545 | .then(() => {
|
466 | 546 | throw er
|
|
470 | 550 | // adding to the trash list will skip reifying, and delete them
|
471 | 551 | // if they are currently in the tree and otherwise untouched.
|
472 | 552 | [_addOmitsToTrashList] () {
|
473 |
| - if (!this[_omitDev] && !this[_omitOptional] && !this[_omitPeer]) { |
| 553 | + if (!this.#omitDev && !this.#omitOptional && !this.#omitPeer) { |
474 | 554 | return
|
475 | 555 | }
|
476 | 556 |
|
|
492 | 572 |
|
493 | 573 | // omit node if the dep type matches any omit flags that were set
|
494 | 574 | if (
|
495 |
| - node.peer && this[_omitPeer] || |
496 |
| - node.dev && this[_omitDev] || |
497 |
| - node.optional && this[_omitOptional] || |
498 |
| - node.devOptional && this[_omitOptional] && this[_omitDev] |
| 575 | + node.peer && this.#omitPeer || |
| 576 | + node.dev && this.#omitDev || |
| 577 | + node.optional && this.#omitOptional || |
| 578 | + node.devOptional && this.#omitOptional && this.#omitDev |
499 | 579 | ) {
|
500 | 580 | this[_addNodeToTrashList](node)
|
501 | 581 | }
|
|
511 | 591 | const leaves = this.diff.leaves
|
512 | 592 | .filter(diff => {
|
513 | 593 | return (diff.action === 'ADD' || diff.action === 'CHANGE') &&
|
514 |
| - !this[_sparseTreeDirs].has(diff.ideal.path) && |
| 594 | + !this.#sparseTreeDirs.has(diff.ideal.path) && |
515 | 595 | !diff.ideal.isLink
|
516 | 596 | })
|
517 | 597 | .map(diff => diff.ideal)
|
|
528 | 608 | continue
|
529 | 609 | }
|
530 | 610 | dirsChecked.add(d)
|
531 | 611 | const st = await lstat(d).catch(er => null)
|
532 | 612 | // this can happen if we have a link to a package with a name
|
533 | 613 | // that the filesystem treats as if it is the same thing.
|
534 | 614 | // would be nice to have conditional istanbul ignores here...
|
535 | 615 | /* istanbul ignore next - defense in depth */
|
536 | 616 | if (st && !st.isDirectory()) {
|
537 | 617 | const retired = retirePath(d)
|
538 |
| - this[_retiredPaths][d] = retired |
| 618 | + this.#retiredPaths[d] = retired |
539 | 619 | this[_trashList].add(retired)
|
540 | 620 | await this[_renamePath](d, retired)
|
541 | 621 | }
|
542 | 622 | }
|
543 |
| - this[_sparseTreeDirs].add(node.path) |
| 623 | + this.#sparseTreeDirs.add(node.path) |
544 | 624 | const made = await mkdir(node.path, { recursive: true })
|
545 | 625 | // if the directory already exists, made will be undefined. if that's the case
|
546 | 626 | // we don't want to remove it because we aren't the ones who created it so we
|
547 |
| - // omit it from the _sparseTreeRoots |
| 627 | + // omit it from the #sparseTreeRoots |
548 | 628 | if (made) {
|
549 |
| - this[_sparseTreeRoots].add(made) |
| 629 | + this.#sparseTreeRoots.add(made) |
550 | 630 | }
|
551 | 631 | })).then(timeEnd)
|
552 | 632 | }
|
553 | 633 |
|
554 | 634 | [_rollbackCreateSparseTree] (er) {
|
555 | 635 | const timeEnd = time.start('reify:rollback:createSparse')
|
556 | 636 | // cut the roots of the sparse tree that were created, not the leaves
|
557 |
| - const roots = this[_sparseTreeRoots] |
| 637 | + const roots = this.#sparseTreeRoots |
558 | 638 | // also delete the moves that we retired, so that we can move them back
|
559 | 639 | const failures = []
|
560 |
| - const targets = [...roots, ...Object.keys(this[_retiredPaths])] |
| 640 | + const targets = [...roots, ...Object.keys(this.#retiredPaths)] |
561 | 641 | const unlinks = targets
|
562 | 642 | .map(path => rm(path, { recursive: true, force: true }).catch(er => failures.push([path, er])))
|
563 | 643 | return promiseAllRejectLate(unlinks).then(() => {
|
|
574 | 654 | // we need to unpack them, read that shrinkwrap file, and then update
|
575 | 655 | // the tree by calling loadVirtual with the node as the root.
|
576 | 656 | [_loadShrinkwrapsAndUpdateTrees] () {
|
577 |
| - const seen = this[_shrinkwrapInflated] |
| 657 | + const seen = this.#shrinkwrapInflated |
578 | 658 | const shrinkwraps = this.diff.leaves
|
579 | 659 | .filter(d => (d.action === 'CHANGE' || d.action === 'ADD' || !d.action) &&
|
580 | 660 | d.ideal.hasShrinkwrap && !seen.has(d.ideal) &&
|
|
632 | 712 | checkPlatform(node.package, false, { cpu, os, libc })
|
633 | 713 | }
|
634 | 714 | await this[_checkBins](node)
|
635 |
| - await this[_extractOrLink](node) |
636 |
| - await this[_warnDeprecated](node) |
| 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 | + } |
637 | 722 | })
|
638 | 723 |
|
639 | 724 | return this[_handleOptionalFailure](node, p)
|
|
645 | 730 | }
|
646 | 731 |
|
647 | 732 | // do not allow node_modules to be a symlink
|
648 |
| - async [_validateNodeModules] (nm) { |
649 |
| - if (this.options.force || this[_nmValidated].has(nm)) { |
| 733 | + async #validateNodeModules (nm) { |
| 734 | + if (this.options.force || this.#nmValidated.has(nm)) { |
650 | 735 | return
|
651 | 736 | }
|
652 | 737 | const st = await lstat(nm).catch(() => null)
|
653 | 738 | if (!st || st.isDirectory()) {
|
654 |
| - this[_nmValidated].add(nm) |
| 739 | + this.#nmValidated.add(nm) |
655 | 740 | return
|
656 | 741 | }
|
657 | 742 | log.warn('reify', 'Removing non-directory', nm)
|
658 | 743 | await rm(nm, { recursive: true, force: true })
|
659 | 744 | }
|
660 | 745 |
|
661 |
| - async [_extractOrLink] (node) { |
| 746 | + async #extractOrLink (node) { |
662 | 747 | const nm = resolve(node.parent.path, 'node_modules')
|
663 |
| - await this[_validateNodeModules](nm) |
| 748 | + await this.#validateNodeModules(nm) |
664 | 749 |
|
665 | 750 | if (!node.isLink) {
|
666 | 751 | // in normal cases, node.resolved should *always* be set by now.
|
|
672 | 757 | // entirely, since we can't possibly reify it.
|
673 | 758 | let res = null
|
674 | 759 | if (node.resolved) {
|
675 |
| - const registryResolved = this[_registryResolved](node.resolved) |
| 760 | + const registryResolved = this.#registryResolved(node.resolved) |
676 | 761 | if (registryResolved) {
|
677 | 762 | res = `${node.name}@${registryResolved}`
|
678 | 763 | }
|
|
694 | 779 | return
|
695 | 780 | }
|
696 | 781 | await debug(async () => {
|
697 | 782 | const st = await lstat(node.path).catch(e => null)
|
698 | 783 | if (st && !st.isDirectory()) {
|
699 | 784 | debug.log('unpacking into a non-directory', node)
|
700 | 785 | throw Object.assign(new Error('ENOTDIR: not a directory'), {
|
|
718 | 803 |
|
719 | 804 | // node.isLink
|
720 | 805 | await rm(node.path, { recursive: true, force: true })
|
721 |
| - await this[_symlink](node) |
722 |
| - } |
723 | 806 |
|
724 |
| - async [_symlink] (node) { |
| 807 | + // symlink |
725 | 808 | const dir = dirname(node.path)
|
726 | 809 | const target = node.realpath
|
727 | 810 | const rel = relative(dir, target)
|
728 | 811 | await mkdir(dir, { recursive: true })
|
729 | 812 | return symlink(rel, node.path, 'junction')
|
730 | 813 | }
|
731 | 814 |
|
732 |
| - [_warnDeprecated] (node) { |
733 |
| - const { _id, deprecated } = node.package |
734 |
| - if (deprecated) { |
735 |
| - log.warn('deprecated', `${_id}: ${deprecated}`) |
736 |
| - } |
737 |
| - } |
738 |
| - |
739 | 815 | // if the node is optional, then the failure of the promise is nonfatal
|
740 | 816 | // just add it and its optional set to the trash list.
|
741 | 817 | [_handleOptionalFailure] (node, p) {
|
|
748 | 824 | }) : p).then(() => node)
|
749 | 825 | }
|
750 | 826 |
|
751 |
| - [_registryResolved] (resolved) { |
| 827 | + #registryResolved (resolved) { |
752 | 828 | // the default registry url is a magic value meaning "the currently
|
753 | 829 | // configured registry".
|
754 | 830 | // `resolved` must never be falsey.
|
|
778 | 854 | // by the contents of the package. however, in their case, rather than
|
779 | 855 | // shipping a virtual tree that must be reified, they ship an entire
|
780 | 856 | // reified actual tree that must be unpacked and not modified.
|
781 |
| - [_loadBundlesAndUpdateTrees] ( |
782 |
| - depth = 0, bundlesByDepth = this[_getBundlesByDepth]() |
783 |
| - ) { |
| 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 | + |
784 | 891 | if (depth === 0) {
|
785 | 892 | time.start('reify:loadBundles')
|
786 | 893 | }
|
787 | 894 |
|
788 |
| - const maxBundleDepth = bundlesByDepth.get('maxBundleDepth') |
789 | 895 | if (depth > maxBundleDepth) {
|
790 | 896 | // if we did something, then prune the tree and update the diffs
|
791 | 897 | if (maxBundleDepth !== -1) {
|
792 |
| - this[_pruneBundledMetadeps](bundlesByDepth) |
| 898 | + this.#pruneBundledMetadeps(bundlesByDepth) |
793 | 899 | this[_diffTrees]()
|
794 | 900 | }
|
795 | 901 | time.end('reify:loadBundles')
|
|
810 | 916 | // extract all the nodes with bundles
|
811 | 917 | return promiseCallLimit(set.map(node => {
|
812 | 918 | return () => {
|
813 |
| - this[_bundleUnpacked].add(node) |
| 919 | + this.#bundleUnpacked.add(node) |
814 | 920 | return this[_reifyNode](node)
|
815 | 921 | }
|
816 | 922 | }), { rejectLate: true })
|
|
839 | 945 | },
|
840 | 946 | })
|
841 | 947 | for (const name of notTransplanted) {
|
842 |
| - this[_bundleMissing].add(node.children.get(name)) |
| 948 | + this.#bundleMissing.add(node.children.get(name)) |
843 | 949 | }
|
844 | 950 | })))
|
845 | 951 | // move onto the next level of bundled items
|
846 | 952 | .then(() => this[_loadBundlesAndUpdateTrees](depth + 1, bundlesByDepth))
|
847 | 953 | }
|
848 | 954 |
|
849 |
| - [_getBundlesByDepth] () { |
850 |
| - const bundlesByDepth = new Map() |
851 |
| - let maxBundleDepth = -1 |
852 |
| - dfwalk({ |
853 |
| - tree: this.diff, |
854 |
| - visit: diff => { |
855 |
| - const node = diff.ideal |
856 |
| - if (!node) { |
857 |
| - return |
858 |
| - } |
859 |
| - if (node.isProjectRoot) { |
860 |
| - return |
861 |
| - } |
862 |
| - |
863 |
| - const { bundleDependencies } = node.package |
864 |
| - if (bundleDependencies && bundleDependencies.length) { |
865 |
| - maxBundleDepth = Math.max(maxBundleDepth, node.depth) |
866 |
| - if (!bundlesByDepth.has(node.depth)) { |
867 |
| - bundlesByDepth.set(node.depth, [node]) |
868 |
| - } else { |
869 |
| - bundlesByDepth.get(node.depth).push(node) |
870 |
| - } |
871 |
| - } |
872 |
| - }, |
873 |
| - getChildren: diff => diff.children, |
874 |
| - }) |
875 |
| - |
876 |
| - bundlesByDepth.set('maxBundleDepth', maxBundleDepth) |
877 |
| - return bundlesByDepth |
878 |
| - } |
879 |
| - |
880 | 955 | // https://github.com/npm/cli/issues/1597#issuecomment-667639545
|
881 |
| - [_pruneBundledMetadeps] (bundlesByDepth) { |
| 956 | + #pruneBundledMetadeps (bundlesByDepth) { |
882 | 957 | const bundleShadowed = new Set()
|
883 | 958 |
|
884 | 959 | // Example dep graph:
|
|
1012 | 1087 | }
|
1013 | 1088 |
|
1014 | 1089 | const node = diff.ideal
|
1015 |
| - const bd = this[_bundleUnpacked].has(node) |
1016 |
| - const sw = this[_shrinkwrapInflated].has(node) |
1017 |
| - const bundleMissing = this[_bundleMissing].has(node) |
| 1090 | + const bd = this.#bundleUnpacked.has(node) |
| 1091 | + const sw = this.#shrinkwrapInflated.has(node) |
| 1092 | + const bundleMissing = this.#bundleMissing.has(node) |
1018 | 1093 |
|
1019 | 1094 | // check whether we still need to unpack this one.
|
1020 | 1095 | // test the inDepBundle last, since that's potentially a tree walk.
|
|
1050 | 1125 | // the actualTree and idealTree _don't_ differ, starting from the
|
1051 | 1126 | // shallowest nodes that we moved aside in the first place.
|
1052 | 1127 | const timeEnd = time.start('reify:unretire')
|
1053 |
| - const moves = this[_retiredPaths] |
1054 |
| - this[_retiredUnchanged] = {} |
| 1128 | + const moves = this.#retiredPaths |
| 1129 | + this.#retiredUnchanged = {} |
1055 | 1130 | return promiseAllRejectLate(this.diff.children.map(diff => {
|
1056 | 1131 | // skip if nothing was retired
|
1057 | 1132 | if (diff.action !== 'CHANGE' && diff.action !== 'REMOVE') {
|
|
1074 | 1149 | }
|
1075 | 1150 | })
|
1076 | 1151 |
|
1077 |
| - this[_retiredUnchanged][retireFolder] = [] |
| 1152 | + this.#retiredUnchanged[retireFolder] = [] |
1078 | 1153 | return promiseAllRejectLate(diff.unchanged.map(node => {
|
1079 | 1154 | // no need to roll back links, since we'll just delete them anyway
|
1080 | 1155 | if (node.isLink) {
|
|
1083 | 1158 | }
|
1084 | 1159 |
|
1085 | 1160 | // will have been moved/unpacked along with bundler
|
1086 |
| - if (node.inDepBundle && !this[_bundleMissing].has(node)) { |
| 1161 | + if (node.inDepBundle && !this.#bundleMissing.has(node)) { |
1087 | 1162 | return
|
1088 | 1163 | }
|
1089 | 1164 |
|
1090 |
| - this[_retiredUnchanged][retireFolder].push(node) |
| 1165 | + this.#retiredUnchanged[retireFolder].push(node) |
1091 | 1166 |
|
1092 | 1167 | const rel = relative(realFolder, node.path)
|
1093 | 1168 | const fromPath = resolve(retireFolder, rel)
|
|
1114 | 1189 | }
|
1115 | 1190 |
|
1116 | 1191 | [_rollbackMoveBackRetiredUnchanged] (er) {
|
1117 |
| - const moves = this[_retiredPaths] |
| 1192 | + const moves = this.#retiredPaths |
1118 | 1193 | // flip the mapping around to go back
|
1119 | 1194 | const realFolders = new Map(Object.entries(moves).map(([k, v]) => [v, k]))
|
1120 |
| - const promises = Object.entries(this[_retiredUnchanged]) |
| 1195 | + const promises = Object.entries(this.#retiredUnchanged) |
1121 | 1196 | .map(([retireFolder, nodes]) => promiseAllRejectLate(nodes.map(node => {
|
1122 | 1197 | const realFolder = realFolders.get(retireFolder)
|
1123 | 1198 | const rel = relative(realFolder, node.path)
|
|
1209 | 1284 | const saveIdealTree = !(
|
1210 | 1285 | (!save && !hasUpdates)
|
1211 | 1286 | || this.options.global
|
1212 |
| - || this[_dryRun] |
| 1287 | + || this.options.dryRun |
1213 | 1288 | )
|
1214 | 1289 |
|
1215 | 1290 | if (!saveIdealTree) {
|
|
1245 | 1320 | const isLocalDep = req.type === 'directory' || req.type === 'file'
|
1246 | 1321 | if (req.registry) {
|
1247 | 1322 | const version = child.version
|
1248 |
| - const prefixRange = version ? this[_savePrefix] + version : '*' |
| 1323 | + const prefixRange = version ? this.options.savePrefix + version : '*' |
1249 | 1324 | // if we installed a range, then we save the range specified
|
1250 | 1325 | // if it is not a subset of the ^x.y.z. eg, installing a range
|
1251 | 1326 | // of `1.x <1.2.3` will not be saved as `^1.2.0`, because that
|
|
1280 | 1355 | // using their relative path
|
1281 | 1356 | if (edge.type === 'workspace') {
|
1282 | 1357 | const { version } = edge.to.target
|
1283 |
| - const prefixRange = version ? this[_savePrefix] + version : '*' |
| 1358 | + const prefixRange = version ? this.options.savePrefix + version : '*' |
1284 | 1359 | newSpec = prefixRange
|
1285 | 1360 | } else {
|
1286 | 1361 | // save the relative path in package.json
|
|
1449 | 1524 |
|
1450 | 1525 | // TODO this ignores options.save
|
1451 | 1526 | await this.idealTree.meta.save({
|
1452 |
| - format: (this[_formatPackageLock] && format) ? format |
1453 |
| - : this[_formatPackageLock], |
| 1527 | + format: (this.options.formatPackageLock && format) ? format |
| 1528 | + : this.options.formatPackageLock, |
1454 | 1529 | })
|
1455 | 1530 | }
|
1456 | 1531 |
|
1457 | 1532 | timeEnd()
|
1458 | 1533 | return true
|
1459 | 1534 | }
|
1460 |
| - |
1461 |
| - async [_copyIdealToActual] () { |
1462 |
| - // clean up any trash that is still in the tree |
1463 |
| - for (const path of this[_trashList]) { |
1464 |
| - const loc = relpath(this.idealTree.realpath, path) |
1465 |
| - const node = this.idealTree.inventory.get(loc) |
1466 |
| - if (node && node.root === this.idealTree) { |
1467 |
| - node.parent = null |
1468 |
| - } |
1469 |
| - } |
1470 |
| - |
1471 |
| - // if we filtered to only certain nodes, then anything ELSE needs |
1472 |
| - // to be untouched in the resulting actual tree, even if it differs |
1473 |
| - // in the idealTree. Copy over anything that was in the actual and |
1474 |
| - // was not changed, delete anything in the ideal and not actual. |
1475 |
| - // Then we move the entire idealTree over to this.actualTree, and |
1476 |
| - // save the hidden lockfile. |
1477 |
| - if (this.diff && this.diff.filterSet.size) { |
1478 |
| - const reroot = new Set() |
1479 |
| - |
1480 |
| - const { filterSet } = this.diff |
1481 |
| - const seen = new Set() |
1482 |
| - for (const [loc, ideal] of this.idealTree.inventory.entries()) { |
1483 |
| - seen.add(loc) |
1484 |
| - |
1485 |
| - // if it's an ideal node from the filter set, then skip it |
1486 |
| - // because we already made whatever changes were necessary |
1487 |
| - if (filterSet.has(ideal)) { |
1488 |
| - continue |
1489 |
| - } |
1490 |
| - |
1491 |
| - // otherwise, if it's not in the actualTree, then it's not a thing |
1492 |
| - // that we actually added. And if it IS in the actualTree, then |
1493 |
| - // it's something that we left untouched, so we need to record |
1494 |
| - // that. |
1495 |
| - const actual = this.actualTree.inventory.get(loc) |
1496 |
| - if (!actual) { |
1497 |
| - ideal.root = null |
1498 |
| - } else { |
1499 |
| - if ([...actual.linksIn].some(link => filterSet.has(link))) { |
1500 |
| - seen.add(actual.location) |
1501 |
| - continue |
1502 |
| - } |
1503 |
| - const { realpath, isLink } = actual |
1504 |
| - if (isLink && ideal.isLink && ideal.realpath === realpath) { |
1505 |
| - continue |
1506 |
| - } else { |
1507 |
| - reroot.add(actual) |
1508 |
| - } |
1509 |
| - } |
1510 |
| - } |
1511 |
| - |
1512 |
| - // now find any actual nodes that may not be present in the ideal |
1513 |
| - // tree, but were left behind by virtue of not being in the filter |
1514 |
| - for (const [loc, actual] of this.actualTree.inventory.entries()) { |
1515 |
| - if (seen.has(loc)) { |
1516 |
| - continue |
1517 |
| - } |
1518 |
| - seen.add(loc) |
1519 |
| - |
1520 |
| - // we know that this is something that ISN'T in the idealTree, |
1521 |
| - // or else we will have addressed it in the previous loop. |
1522 |
| - // If it's in the filterSet, that means we intentionally removed |
1523 |
| - // it, so nothing to do here. |
1524 |
| - if (filterSet.has(actual)) { |
1525 |
| - continue |
1526 |
| - } |
1527 |
| - |
1528 |
| - reroot.add(actual) |
1529 |
| - } |
1530 |
| - |
1531 |
| - // go through the rerooted actual nodes, and move them over. |
1532 |
| - for (const actual of reroot) { |
1533 |
| - actual.root = this.idealTree |
1534 |
| - } |
1535 |
| - |
1536 |
| - // prune out any tops that lack a linkIn, they are no longer relevant. |
1537 |
| - for (const top of this.idealTree.tops) { |
1538 |
| - if (top.linksIn.size === 0) { |
1539 |
| - top.root = null |
1540 |
| - } |
1541 |
| - } |
1542 |
| - |
1543 |
| - // need to calculate dep flags, since nodes may have been marked |
1544 |
| - // as extraneous or otherwise incorrect during transit. |
1545 |
| - calcDepFlags(this.idealTree) |
1546 |
| - } |
1547 |
| - |
1548 |
| - // save the ideal's meta as a hidden lockfile after we actualize it |
1549 |
| - this.idealTree.meta.filename = |
1550 |
| - this.idealTree.realpath + '/node_modules/.package-lock.json' |
1551 |
| - this.idealTree.meta.hiddenLockfile = true |
1552 |
| - this.idealTree.meta.lockfileVersion = defaultLockfileVersion |
1553 |
| - |
1554 |
| - this.actualTree = this.idealTree |
1555 |
| - this.idealTree = null |
1556 |
| - |
1557 |
| - if (!this.options.global) { |
1558 |
| - await this.actualTree.meta.save() |
1559 |
| - const ignoreScripts = !!this.options.ignoreScripts |
1560 |
| - // if we aren't doing a dry run or ignoring scripts and we actually made changes to the dep |
1561 |
| - // tree, then run the dependencies scripts |
1562 |
| - if (!this[_dryRun] && !ignoreScripts && this.diff && this.diff.children.length) { |
1563 |
| - const { path, package: pkg } = this.actualTree.target |
1564 |
| - const stdio = this.options.foregroundScripts ? 'inherit' : 'pipe' |
1565 |
| - const { scripts = {} } = pkg |
1566 |
| - for (const event of ['predependencies', 'dependencies', 'postdependencies']) { |
1567 |
| - if (Object.prototype.hasOwnProperty.call(scripts, event)) { |
1568 |
| - log.info('run', pkg._id, event, scripts[event]) |
1569 |
| - await time.start(`reify:run:${event}`, () => runScript({ |
1570 |
| - event, |
1571 |
| - path, |
1572 |
| - pkg, |
1573 |
| - stdio, |
1574 |
| - scriptShell: this.options.scriptShell, |
1575 |
| - })) |
1576 |
| - } |
1577 |
| - } |
1578 |
| - } |
1579 |
| - } |
1580 |
| - } |
1581 |
| - |
1582 |
| - async dedupe (options = {}) { |
1583 |
| - // allow the user to set options on the ctor as well. |
1584 |
| - // XXX: deprecate separate method options objects. |
1585 |
| - options = { ...this.options, ...options } |
1586 |
| - const tree = await this.loadVirtual().catch(() => this.loadActual()) |
1587 |
| - const names = [] |
1588 |
| - for (const name of tree.inventory.query('name')) { |
1589 |
| - if (tree.inventory.query('name', name).size > 1) { |
1590 |
| - names.push(name) |
1591 |
| - } |
1592 |
| - } |
1593 |
| - return this.reify({ |
1594 |
| - ...options, |
1595 |
| - preferDedupe: true, |
1596 |
| - update: { names }, |
1597 |
| - }) |
1598 |
| - } |
1599 | 1535 | }
|
0 commit comments