@npmcli/arborist 7.2.0 → 7.2.2
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/lib/arborist/load-actual.js +1 -1
- package/lib/shrinkwrap.js +247 -221
- package/lib/tree-check.js +1 -1
- package/lib/yarn-lock.js +4 -4
- package/package.json +7 -7
|
@@ -333,7 +333,7 @@ module.exports = cls => class ActualLoader extends cls {
|
|
|
333
333
|
|
|
334
334
|
async #loadFSTree (node) {
|
|
335
335
|
const did = this.#actualTreeLoaded
|
|
336
|
-
if (!did.has(node.target.realpath)) {
|
|
336
|
+
if (!node.isLink && !did.has(node.target.realpath)) {
|
|
337
337
|
did.add(node.target.realpath)
|
|
338
338
|
await this.#loadFSChildren(node.target)
|
|
339
339
|
return Promise.all(
|
package/lib/shrinkwrap.js
CHANGED
|
@@ -48,7 +48,7 @@ const { resolve, basename, relative } = require('path')
|
|
|
48
48
|
const specFromLock = require('./spec-from-lock.js')
|
|
49
49
|
const versionFromTgz = require('./version-from-tgz.js')
|
|
50
50
|
const npa = require('npm-package-arg')
|
|
51
|
-
const
|
|
51
|
+
const pkgJson = require('@npmcli/package-json')
|
|
52
52
|
const parseJSON = require('parse-conflict-json')
|
|
53
53
|
|
|
54
54
|
const stringify = require('json-stringify-nice')
|
|
@@ -81,28 +81,6 @@ const relpath = require('./relpath.js')
|
|
|
81
81
|
const consistentResolve = require('./consistent-resolve.js')
|
|
82
82
|
const { overrideResolves } = require('./override-resolves.js')
|
|
83
83
|
|
|
84
|
-
const maybeReadFile = file => {
|
|
85
|
-
return readFile(file, 'utf8').then(d => d, er => {
|
|
86
|
-
/* istanbul ignore else - can't test without breaking module itself */
|
|
87
|
-
if (er.code === 'ENOENT') {
|
|
88
|
-
return ''
|
|
89
|
-
} else {
|
|
90
|
-
throw er
|
|
91
|
-
}
|
|
92
|
-
})
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
const maybeStatFile = file => {
|
|
96
|
-
return stat(file).then(st => st.isFile(), er => {
|
|
97
|
-
/* istanbul ignore else - can't test without breaking module itself */
|
|
98
|
-
if (er.code === 'ENOENT') {
|
|
99
|
-
return null
|
|
100
|
-
} else {
|
|
101
|
-
throw er
|
|
102
|
-
}
|
|
103
|
-
})
|
|
104
|
-
}
|
|
105
|
-
|
|
106
84
|
const pkgMetaKeys = [
|
|
107
85
|
// note: name is included if necessary, for alias packages
|
|
108
86
|
'version',
|
|
@@ -134,81 +112,72 @@ const nodeMetaKeys = [
|
|
|
134
112
|
|
|
135
113
|
const metaFieldFromPkg = (pkg, key) => {
|
|
136
114
|
const val = pkg[key]
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
115
|
+
if (val) {
|
|
116
|
+
// get only the license type, not the full object
|
|
117
|
+
if (key === 'license' && typeof val === 'object' && val.type) {
|
|
118
|
+
return val.type
|
|
119
|
+
}
|
|
140
120
|
// skip empty objects and falsey values
|
|
141
|
-
|
|
142
|
-
|
|
121
|
+
if (typeof val !== 'object' || Object.keys(val).length) {
|
|
122
|
+
return val
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
return null
|
|
143
126
|
}
|
|
144
127
|
|
|
145
|
-
// check to make sure that there are no packages newer than the hidden lockfile
|
|
146
|
-
const assertNoNewer = async (path, data, lockTime, dir
|
|
128
|
+
// check to make sure that there are no packages newer than or missing from the hidden lockfile
|
|
129
|
+
const assertNoNewer = async (path, data, lockTime, dir, seen) => {
|
|
147
130
|
const base = basename(dir)
|
|
148
131
|
const isNM = dir !== path && base === 'node_modules'
|
|
149
|
-
const isScope = dir !== path &&
|
|
150
|
-
const isParent = dir === path || isNM || isScope
|
|
132
|
+
const isScope = dir !== path && base.startsWith('@')
|
|
133
|
+
const isParent = (dir === path) || isNM || isScope
|
|
151
134
|
|
|
135
|
+
const parent = isParent ? dir : resolve(dir, 'node_modules')
|
|
152
136
|
const rel = relpath(path, dir)
|
|
153
|
-
|
|
154
|
-
|
|
137
|
+
seen.add(rel)
|
|
138
|
+
let entries
|
|
139
|
+
if (dir === path) {
|
|
140
|
+
entries = [{ name: 'node_modules', isDirectory: () => true }]
|
|
141
|
+
} else {
|
|
142
|
+
const { mtime: dirTime } = await stat(dir)
|
|
155
143
|
if (dirTime > lockTime) {
|
|
156
|
-
throw
|
|
144
|
+
throw new Error(`out of date, updated: ${rel}`)
|
|
157
145
|
}
|
|
158
146
|
if (!isScope && !isNM && !data.packages[rel]) {
|
|
159
|
-
throw
|
|
147
|
+
throw new Error(`missing from lockfile: ${rel}`)
|
|
160
148
|
}
|
|
161
|
-
|
|
162
|
-
} else {
|
|
163
|
-
seen = new Set([rel])
|
|
149
|
+
entries = await readdir(parent, { withFileTypes: true }).catch(() => [])
|
|
164
150
|
}
|
|
165
151
|
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
const ents = await children.catch(() => [])
|
|
172
|
-
await Promise.all(ents.map(async ent => {
|
|
173
|
-
const child = resolve(parent, ent.name)
|
|
174
|
-
if (ent.isDirectory() && !/^\./.test(ent.name)) {
|
|
152
|
+
// TODO limit concurrency here, this is recursive
|
|
153
|
+
await Promise.all(entries.map(async dirent => {
|
|
154
|
+
const child = resolve(parent, dirent.name)
|
|
155
|
+
if (dirent.isDirectory() && !dirent.name.startsWith('.')) {
|
|
175
156
|
await assertNoNewer(path, data, lockTime, child, seen)
|
|
176
|
-
} else if (
|
|
157
|
+
} else if (dirent.isSymbolicLink()) {
|
|
177
158
|
const target = resolve(parent, await readlink(child))
|
|
178
159
|
const tstat = await stat(target).catch(
|
|
179
160
|
/* istanbul ignore next - windows */ () => null)
|
|
180
161
|
seen.add(relpath(path, child))
|
|
181
162
|
/* istanbul ignore next - windows cannot do this */
|
|
182
|
-
if (tstat
|
|
163
|
+
if (tstat?.isDirectory() && !seen.has(relpath(path, target))) {
|
|
183
164
|
await assertNoNewer(path, data, lockTime, target, seen)
|
|
184
165
|
}
|
|
185
166
|
}
|
|
186
167
|
}))
|
|
168
|
+
|
|
187
169
|
if (dir !== path) {
|
|
188
170
|
return
|
|
189
171
|
}
|
|
190
172
|
|
|
191
173
|
// assert that all the entries in the lockfile were seen
|
|
192
|
-
for (const loc
|
|
174
|
+
for (const loc in data.packages) {
|
|
193
175
|
if (!seen.has(loc)) {
|
|
194
|
-
throw
|
|
176
|
+
throw new Error(`missing from node_modules: ${loc}`)
|
|
195
177
|
}
|
|
196
178
|
}
|
|
197
179
|
}
|
|
198
180
|
|
|
199
|
-
const _awaitingUpdate = Symbol('_awaitingUpdate')
|
|
200
|
-
const _updateWaitingNode = Symbol('_updateWaitingNode')
|
|
201
|
-
const _lockFromLoc = Symbol('_lockFromLoc')
|
|
202
|
-
const _pathToLoc = Symbol('_pathToLoc')
|
|
203
|
-
const _loadAll = Symbol('_loadAll')
|
|
204
|
-
const _metaFromLock = Symbol('_metaFromLock')
|
|
205
|
-
const _resolveMetaNode = Symbol('_resolveMetaNode')
|
|
206
|
-
const _fixDependencies = Symbol('_fixDependencies')
|
|
207
|
-
const _buildLegacyLockfile = Symbol('_buildLegacyLockfile')
|
|
208
|
-
const _filenameSet = Symbol('_filenameSet')
|
|
209
|
-
const _maybeRead = Symbol('_maybeRead')
|
|
210
|
-
const _maybeStat = Symbol('_maybeStat')
|
|
211
|
-
|
|
212
181
|
class Shrinkwrap {
|
|
213
182
|
static get defaultLockfileVersion () {
|
|
214
183
|
return defaultLockfileVersion
|
|
@@ -228,13 +197,18 @@ class Shrinkwrap {
|
|
|
228
197
|
const s = new Shrinkwrap(options)
|
|
229
198
|
s.reset()
|
|
230
199
|
|
|
231
|
-
const [sw, lock] = await s
|
|
200
|
+
const [sw, lock] = await s.resetFiles
|
|
232
201
|
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
202
|
+
// XXX this is duplicated in this.load(), but using loadFiles instead of resetFiles
|
|
203
|
+
if (s.hiddenLockfile) {
|
|
204
|
+
s.filename = resolve(s.path, 'node_modules/.package-lock.json')
|
|
205
|
+
} else if (s.shrinkwrapOnly || sw) {
|
|
206
|
+
s.filename = resolve(s.path, 'npm-shrinkwrap.json')
|
|
207
|
+
} else {
|
|
208
|
+
s.filename = resolve(s.path, 'package-lock.json')
|
|
209
|
+
}
|
|
237
210
|
s.loadedFromDisk = !!(sw || lock)
|
|
211
|
+
// TODO what uses this?
|
|
238
212
|
s.type = basename(s.filename)
|
|
239
213
|
|
|
240
214
|
return s
|
|
@@ -249,12 +223,12 @@ class Shrinkwrap {
|
|
|
249
223
|
}
|
|
250
224
|
|
|
251
225
|
const meta = {}
|
|
252
|
-
|
|
226
|
+
for (const key of pkgMetaKeys) {
|
|
253
227
|
const val = metaFieldFromPkg(node.package, key)
|
|
254
228
|
if (val) {
|
|
255
229
|
meta[key.replace(/^_/, '')] = val
|
|
256
230
|
}
|
|
257
|
-
}
|
|
231
|
+
}
|
|
258
232
|
// we only include name if different from the node path name, and for the
|
|
259
233
|
// root to help prevent churn based on the name of the directory the
|
|
260
234
|
// project is in
|
|
@@ -267,11 +241,11 @@ class Shrinkwrap {
|
|
|
267
241
|
meta.devDependencies = node.package.devDependencies
|
|
268
242
|
}
|
|
269
243
|
|
|
270
|
-
|
|
244
|
+
for (const key of nodeMetaKeys) {
|
|
271
245
|
if (node[key]) {
|
|
272
246
|
meta[key] = node[key]
|
|
273
247
|
}
|
|
274
|
-
}
|
|
248
|
+
}
|
|
275
249
|
|
|
276
250
|
const resolved = consistentResolve(node.resolved, node.path, path, true)
|
|
277
251
|
// hide resolved from registry dependencies.
|
|
@@ -302,6 +276,8 @@ class Shrinkwrap {
|
|
|
302
276
|
return meta
|
|
303
277
|
}
|
|
304
278
|
|
|
279
|
+
#awaitingUpdate = new Map()
|
|
280
|
+
|
|
305
281
|
constructor (options = {}) {
|
|
306
282
|
const {
|
|
307
283
|
path,
|
|
@@ -313,11 +289,14 @@ class Shrinkwrap {
|
|
|
313
289
|
resolveOptions = {},
|
|
314
290
|
} = options
|
|
315
291
|
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
292
|
+
if (hiddenLockfile) {
|
|
293
|
+
this.lockfileVersion = 3
|
|
294
|
+
} else if (lockfileVersion) {
|
|
295
|
+
this.lockfileVersion = parseInt(lockfileVersion, 10)
|
|
296
|
+
} else {
|
|
297
|
+
this.lockfileVersion = null
|
|
298
|
+
}
|
|
319
299
|
|
|
320
|
-
this[_awaitingUpdate] = new Map()
|
|
321
300
|
this.tree = null
|
|
322
301
|
this.path = resolve(path || '.')
|
|
323
302
|
this.filename = null
|
|
@@ -354,9 +333,12 @@ class Shrinkwrap {
|
|
|
354
333
|
// don't use the simple version if the "registry" url is
|
|
355
334
|
// something else entirely!
|
|
356
335
|
const tgz = isReg && versionFromTgz(spec.name, resolved) || {}
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
336
|
+
let yspec = resolved
|
|
337
|
+
if (tgz.name === spec.name && tgz.version === version) {
|
|
338
|
+
yspec = version
|
|
339
|
+
} else if (isReg && tgz.name && tgz.version) {
|
|
340
|
+
yspec = `npm:${tgz.name}@${tgz.version}`
|
|
341
|
+
}
|
|
360
342
|
if (yspec) {
|
|
361
343
|
options.resolved = resolved.replace(yarnRegRe, 'https://registry.npmjs.org/')
|
|
362
344
|
options.integrity = integrity
|
|
@@ -370,7 +352,7 @@ class Shrinkwrap {
|
|
|
370
352
|
// still worth doing a load() first so we know which files to write.
|
|
371
353
|
reset () {
|
|
372
354
|
this.tree = null
|
|
373
|
-
this
|
|
355
|
+
this.#awaitingUpdate = new Map()
|
|
374
356
|
const lockfileVersion = this.lockfileVersion || defaultLockfileVersion
|
|
375
357
|
this.originalLockfileVersion = lockfileVersion
|
|
376
358
|
|
|
@@ -382,58 +364,83 @@ class Shrinkwrap {
|
|
|
382
364
|
}
|
|
383
365
|
}
|
|
384
366
|
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
this.path
|
|
367
|
+
// files to potentially read from and write to, in order of priority
|
|
368
|
+
get #filenameSet () {
|
|
369
|
+
if (this.shrinkwrapOnly) {
|
|
370
|
+
return [`${this.path}/npm-shrinkwrap.json`]
|
|
371
|
+
}
|
|
372
|
+
if (this.hiddenLockfile) {
|
|
373
|
+
return [`${this.path}/node_modules/.package-lock.json`]
|
|
374
|
+
}
|
|
375
|
+
return [
|
|
376
|
+
`${this.path}/npm-shrinkwrap.json`,
|
|
377
|
+
`${this.path}/package-lock.json`,
|
|
378
|
+
`${this.path}/yarn.lock`,
|
|
395
379
|
]
|
|
396
380
|
}
|
|
397
381
|
|
|
398
|
-
|
|
399
|
-
return Promise.all(
|
|
382
|
+
get loadFiles () {
|
|
383
|
+
return Promise.all(
|
|
384
|
+
this.#filenameSet.map(file => file && readFile(file, 'utf8').then(d => d, er => {
|
|
385
|
+
/* istanbul ignore else - can't test without breaking module itself */
|
|
386
|
+
if (er.code === 'ENOENT') {
|
|
387
|
+
return ''
|
|
388
|
+
} else {
|
|
389
|
+
throw er
|
|
390
|
+
}
|
|
391
|
+
}))
|
|
392
|
+
)
|
|
400
393
|
}
|
|
401
394
|
|
|
402
|
-
|
|
403
|
-
//
|
|
395
|
+
get resetFiles () {
|
|
396
|
+
// slice out yarn, we only care about lock or shrinkwrap when checking
|
|
404
397
|
// this way, since we're not actually loading the full lock metadata
|
|
405
|
-
return Promise.all(this
|
|
406
|
-
.map(
|
|
398
|
+
return Promise.all(this.#filenameSet.slice(0, 2)
|
|
399
|
+
.map(file => file && stat(file).then(st => st.isFile(), er => {
|
|
400
|
+
/* istanbul ignore else - can't test without breaking module itself */
|
|
401
|
+
if (er.code === 'ENOENT') {
|
|
402
|
+
return null
|
|
403
|
+
} else {
|
|
404
|
+
throw er
|
|
405
|
+
}
|
|
406
|
+
})
|
|
407
|
+
)
|
|
408
|
+
)
|
|
407
409
|
}
|
|
408
410
|
|
|
409
411
|
inferFormattingOptions (packageJSONData) {
|
|
410
|
-
// don't use detect-indent, just pick the first line.
|
|
411
|
-
// if the file starts with {" then we have an indent of '', ie, none
|
|
412
|
-
// which will default to 2 at save time.
|
|
413
412
|
const {
|
|
414
413
|
[Symbol.for('indent')]: indent,
|
|
415
414
|
[Symbol.for('newline')]: newline,
|
|
416
415
|
} = packageJSONData
|
|
417
|
-
|
|
418
|
-
|
|
416
|
+
if (indent !== undefined) {
|
|
417
|
+
this.indent = indent
|
|
418
|
+
}
|
|
419
|
+
if (newline !== undefined) {
|
|
420
|
+
this.newline = newline
|
|
421
|
+
}
|
|
419
422
|
}
|
|
420
423
|
|
|
421
424
|
async load () {
|
|
422
425
|
// we don't need to load package-lock.json except for top of tree nodes,
|
|
423
426
|
// only npm-shrinkwrap.json.
|
|
424
|
-
|
|
425
|
-
|
|
427
|
+
let data
|
|
428
|
+
try {
|
|
429
|
+
const [sw, lock, yarn] = await this.loadFiles
|
|
430
|
+
data = sw || lock || '{}'
|
|
426
431
|
|
|
427
432
|
// use shrinkwrap only for deps, otherwise prefer package-lock
|
|
428
433
|
// and ignore npm-shrinkwrap if both are present.
|
|
429
434
|
// TODO: emit a warning here or something if both are present.
|
|
430
|
-
|
|
431
|
-
(this.
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
+
if (this.hiddenLockfile) {
|
|
436
|
+
this.filename = resolve(this.path, 'node_modules/.package-lock.json')
|
|
437
|
+
} else if (this.shrinkwrapOnly || sw) {
|
|
438
|
+
this.filename = resolve(this.path, 'npm-shrinkwrap.json')
|
|
439
|
+
} else {
|
|
440
|
+
this.filename = resolve(this.path, 'package-lock.json')
|
|
441
|
+
}
|
|
435
442
|
this.type = basename(this.filename)
|
|
436
|
-
this.loadedFromDisk =
|
|
443
|
+
this.loadedFromDisk = Boolean(sw || lock)
|
|
437
444
|
|
|
438
445
|
if (yarn) {
|
|
439
446
|
this.yarnLock = new YarnLock()
|
|
@@ -445,85 +452,84 @@ class Shrinkwrap {
|
|
|
445
452
|
}
|
|
446
453
|
}
|
|
447
454
|
|
|
448
|
-
|
|
449
|
-
}).then(async data => {
|
|
455
|
+
data = parseJSON(data)
|
|
450
456
|
this.inferFormattingOptions(data)
|
|
451
457
|
|
|
452
|
-
if (
|
|
453
|
-
|
|
458
|
+
if (this.hiddenLockfile && data.packages) {
|
|
459
|
+
// add a few ms just to account for jitter
|
|
460
|
+
const lockTime = +(await stat(this.filename)).mtime + 10
|
|
461
|
+
await assertNoNewer(this.path, data, lockTime, this.path, new Set())
|
|
454
462
|
}
|
|
455
463
|
|
|
456
|
-
// add a few ms just to account for jitter
|
|
457
|
-
const lockTime = +(await stat(this.filename)).mtime + 10
|
|
458
|
-
await assertNoNewer(this.path, data, lockTime)
|
|
459
|
-
|
|
460
464
|
// all good! hidden lockfile is the newest thing in here.
|
|
461
|
-
|
|
462
|
-
}).catch(er => {
|
|
465
|
+
} catch (er) {
|
|
463
466
|
/* istanbul ignore else */
|
|
464
467
|
if (typeof this.filename === 'string') {
|
|
465
468
|
const rel = relpath(this.path, this.filename)
|
|
466
|
-
log.verbose('shrinkwrap', `failed to load ${rel}`, er)
|
|
469
|
+
log.verbose('shrinkwrap', `failed to load ${rel}`, er.message)
|
|
467
470
|
} else {
|
|
468
|
-
log.verbose('shrinkwrap', `failed to load ${this.path}`, er)
|
|
471
|
+
log.verbose('shrinkwrap', `failed to load ${this.path}`, er.message)
|
|
469
472
|
}
|
|
470
473
|
this.loadingError = er
|
|
471
474
|
this.loadedFromDisk = false
|
|
472
475
|
this.ancientLockfile = false
|
|
473
|
-
|
|
474
|
-
}
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
}
|
|
476
|
+
data = {}
|
|
477
|
+
}
|
|
478
|
+
// auto convert v1 lockfiles to v3
|
|
479
|
+
// leave v2 in place unless configured
|
|
480
|
+
// v3 by default
|
|
481
|
+
let lockfileVersion = defaultLockfileVersion
|
|
482
|
+
if (this.lockfileVersion) {
|
|
483
|
+
lockfileVersion = this.lockfileVersion
|
|
484
|
+
} else if (data.lockfileVersion && data.lockfileVersion !== 1) {
|
|
485
|
+
lockfileVersion = data.lockfileVersion
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
this.data = {
|
|
489
|
+
...data,
|
|
490
|
+
lockfileVersion,
|
|
491
|
+
requires: true,
|
|
492
|
+
packages: data.packages || {},
|
|
493
|
+
dependencies: data.dependencies || {},
|
|
494
|
+
}
|
|
490
495
|
|
|
491
|
-
|
|
496
|
+
this.originalLockfileVersion = data.lockfileVersion
|
|
492
497
|
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
})
|
|
498
|
+
// use default if it wasn't explicitly set, and the current file is
|
|
499
|
+
// less than our default. otherwise, keep whatever is in the file,
|
|
500
|
+
// unless we had an explicit setting already.
|
|
501
|
+
if (!this.lockfileVersion) {
|
|
502
|
+
this.lockfileVersion = this.data.lockfileVersion = lockfileVersion
|
|
503
|
+
}
|
|
504
|
+
this.ancientLockfile = this.loadedFromDisk &&
|
|
505
|
+
!(data.lockfileVersion >= 2) && !data.requires
|
|
506
|
+
|
|
507
|
+
// load old lockfile deps into the packages listing
|
|
508
|
+
if (data.dependencies && !data.packages) {
|
|
509
|
+
let pkg
|
|
510
|
+
try {
|
|
511
|
+
pkg = await pkgJson.normalize(this.path)
|
|
512
|
+
pkg = pkg.content
|
|
513
|
+
} catch {
|
|
514
|
+
pkg = {}
|
|
511
515
|
}
|
|
512
|
-
|
|
513
|
-
|
|
516
|
+
this.#loadAll('', null, this.data)
|
|
517
|
+
this.#fixDependencies(pkg)
|
|
518
|
+
}
|
|
519
|
+
return this
|
|
514
520
|
}
|
|
515
521
|
|
|
516
|
-
|
|
522
|
+
#loadAll (location, name, lock) {
|
|
517
523
|
// migrate a v1 package lock to the new format.
|
|
518
|
-
const meta = this
|
|
524
|
+
const meta = this.#metaFromLock(location, name, lock)
|
|
519
525
|
// dependencies nested under a link are actually under the link target
|
|
520
526
|
if (meta.link) {
|
|
521
527
|
location = meta.resolved
|
|
522
528
|
}
|
|
523
529
|
if (lock.dependencies) {
|
|
524
|
-
for (const
|
|
530
|
+
for (const name in lock.dependencies) {
|
|
525
531
|
const loc = location + (location ? '/' : '') + 'node_modules/' + name
|
|
526
|
-
this
|
|
532
|
+
this.#loadAll(loc, name, lock.dependencies[name])
|
|
527
533
|
}
|
|
528
534
|
}
|
|
529
535
|
}
|
|
@@ -531,20 +537,20 @@ class Shrinkwrap {
|
|
|
531
537
|
// v1 lockfiles track the optional/dev flags, but they don't tell us
|
|
532
538
|
// which thing had what kind of dep on what other thing, so we need
|
|
533
539
|
// to correct that now, or every link will be considered prod
|
|
534
|
-
|
|
540
|
+
#fixDependencies (pkg) {
|
|
535
541
|
// we need the root package.json because legacy shrinkwraps just
|
|
536
542
|
// have requires:true at the root level, which is even less useful
|
|
537
543
|
// than merging all dep types into one object.
|
|
538
544
|
const root = this.data.packages['']
|
|
539
|
-
|
|
545
|
+
for (const key of pkgMetaKeys) {
|
|
540
546
|
const val = metaFieldFromPkg(pkg, key)
|
|
541
|
-
const k = key.replace(/^_/, '')
|
|
542
547
|
if (val) {
|
|
543
|
-
root[
|
|
548
|
+
root[key.replace(/^_/, '')] = val
|
|
544
549
|
}
|
|
545
|
-
}
|
|
550
|
+
}
|
|
546
551
|
|
|
547
|
-
for (const
|
|
552
|
+
for (const loc in this.data.packages) {
|
|
553
|
+
const meta = this.data.packages[loc]
|
|
548
554
|
if (!meta.requires || !loc) {
|
|
549
555
|
continue
|
|
550
556
|
}
|
|
@@ -555,25 +561,30 @@ class Shrinkwrap {
|
|
|
555
561
|
// This isn't perfect, but it's a pretty good approximation, and at
|
|
556
562
|
// least gets us out of having all 'prod' edges, which throws off the
|
|
557
563
|
// buildIdealTree process
|
|
558
|
-
for (const
|
|
559
|
-
const dep = this
|
|
564
|
+
for (const name in meta.requires) {
|
|
565
|
+
const dep = this.#resolveMetaNode(loc, name)
|
|
560
566
|
// this overwrites the false value set above
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
567
|
+
// default to dependencies if the dep just isn't in the tree, which
|
|
568
|
+
// maybe should be an error, since it means that the shrinkwrap is
|
|
569
|
+
// invalid, but we can't do much better without any info.
|
|
570
|
+
let depType = 'dependencies'
|
|
571
|
+
/* istanbul ignore else - dev deps are only for the root level */
|
|
572
|
+
if (dep?.optional && !meta.optional) {
|
|
573
|
+
depType = 'optionalDependencies'
|
|
574
|
+
} else if (dep?.dev && !meta.dev) {
|
|
575
|
+
// XXX is this even reachable?
|
|
576
|
+
depType = 'devDependencies'
|
|
577
|
+
}
|
|
578
|
+
if (!meta[depType]) {
|
|
579
|
+
meta[depType] = {}
|
|
580
|
+
}
|
|
581
|
+
meta[depType][name] = meta.requires[name]
|
|
571
582
|
}
|
|
572
583
|
delete meta.requires
|
|
573
584
|
}
|
|
574
585
|
}
|
|
575
586
|
|
|
576
|
-
|
|
587
|
+
#resolveMetaNode (loc, name) {
|
|
577
588
|
for (let path = loc; true; path = path.replace(/(^|\/)[^/]*$/, '')) {
|
|
578
589
|
const check = `${path}${path ? '/' : ''}node_modules/${name}`
|
|
579
590
|
if (this.data.packages[check]) {
|
|
@@ -587,7 +598,7 @@ class Shrinkwrap {
|
|
|
587
598
|
return null
|
|
588
599
|
}
|
|
589
600
|
|
|
590
|
-
|
|
601
|
+
#lockFromLoc (lock, path, i = 0) {
|
|
591
602
|
if (!lock) {
|
|
592
603
|
return null
|
|
593
604
|
}
|
|
@@ -604,12 +615,12 @@ class Shrinkwrap {
|
|
|
604
615
|
return null
|
|
605
616
|
}
|
|
606
617
|
|
|
607
|
-
return this
|
|
618
|
+
return this.#lockFromLoc(lock.dependencies[path[i]], path, i + 1)
|
|
608
619
|
}
|
|
609
620
|
|
|
610
621
|
// pass in a path relative to the root path, or an absolute path,
|
|
611
622
|
// get back a /-normalized location based on root path.
|
|
612
|
-
|
|
623
|
+
#pathToLoc (path) {
|
|
613
624
|
return relpath(this.path, resolve(this.path, path))
|
|
614
625
|
}
|
|
615
626
|
|
|
@@ -617,13 +628,13 @@ class Shrinkwrap {
|
|
|
617
628
|
if (!this.data) {
|
|
618
629
|
throw new Error('run load() before getting or setting data')
|
|
619
630
|
}
|
|
620
|
-
const location = this
|
|
621
|
-
this
|
|
631
|
+
const location = this.#pathToLoc(nodePath)
|
|
632
|
+
this.#awaitingUpdate.delete(location)
|
|
622
633
|
|
|
623
634
|
delete this.data.packages[location]
|
|
624
635
|
const path = location.split(/(?:^|\/)node_modules\//)
|
|
625
636
|
const name = path.pop()
|
|
626
|
-
const pLock = this
|
|
637
|
+
const pLock = this.#lockFromLoc(this.data, path)
|
|
627
638
|
if (pLock && pLock.dependencies) {
|
|
628
639
|
delete pLock.dependencies[name]
|
|
629
640
|
}
|
|
@@ -634,9 +645,9 @@ class Shrinkwrap {
|
|
|
634
645
|
throw new Error('run load() before getting or setting data')
|
|
635
646
|
}
|
|
636
647
|
|
|
637
|
-
const location = this
|
|
638
|
-
if (this
|
|
639
|
-
this
|
|
648
|
+
const location = this.#pathToLoc(nodePath)
|
|
649
|
+
if (this.#awaitingUpdate.has(location)) {
|
|
650
|
+
this.#updateWaitingNode(location)
|
|
640
651
|
}
|
|
641
652
|
|
|
642
653
|
// first try to get from the newer spot, which we know has
|
|
@@ -649,12 +660,12 @@ class Shrinkwrap {
|
|
|
649
660
|
// get the node in the shrinkwrap corresponding to this spot
|
|
650
661
|
const path = location.split(/(?:^|\/)node_modules\//)
|
|
651
662
|
const name = path[path.length - 1]
|
|
652
|
-
const lock = this
|
|
663
|
+
const lock = this.#lockFromLoc(this.data, path)
|
|
653
664
|
|
|
654
|
-
return this
|
|
665
|
+
return this.#metaFromLock(location, name, lock)
|
|
655
666
|
}
|
|
656
667
|
|
|
657
|
-
|
|
668
|
+
#metaFromLock (location, name, lock) {
|
|
658
669
|
// This function tries as hard as it can to figure out the metadata
|
|
659
670
|
// from a lockfile which may be outdated or incomplete. Since v1
|
|
660
671
|
// lockfiles used the "version" field to contain a variety of
|
|
@@ -679,7 +690,7 @@ class Shrinkwrap {
|
|
|
679
690
|
// also save the link target, omitting version since we don't know
|
|
680
691
|
// what it is, but we know it isn't a link to itself!
|
|
681
692
|
if (!this.data.packages[target]) {
|
|
682
|
-
this
|
|
693
|
+
this.#metaFromLock(target, name, { ...lock, version: null })
|
|
683
694
|
}
|
|
684
695
|
return this.data.packages[location]
|
|
685
696
|
}
|
|
@@ -799,10 +810,14 @@ class Shrinkwrap {
|
|
|
799
810
|
version,
|
|
800
811
|
} = this.get(node.path)
|
|
801
812
|
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
813
|
+
let pathFixed = null
|
|
814
|
+
if (resolved) {
|
|
815
|
+
if (!/^file:/.test(resolved)) {
|
|
816
|
+
pathFixed = resolved
|
|
817
|
+
} else {
|
|
818
|
+
pathFixed = `file:${resolve(this.path, resolved.slice(5)).replace(/#/g, '%23')}`
|
|
819
|
+
}
|
|
820
|
+
}
|
|
806
821
|
|
|
807
822
|
// if we have one, only set the other if it matches
|
|
808
823
|
// otherwise it could be for a completely different thing.
|
|
@@ -831,7 +846,7 @@ class Shrinkwrap {
|
|
|
831
846
|
node.hasShrinkwrap = node.hasShrinkwrap || hasShrinkwrap || false
|
|
832
847
|
}
|
|
833
848
|
}
|
|
834
|
-
this
|
|
849
|
+
this.#awaitingUpdate.set(loc, node)
|
|
835
850
|
}
|
|
836
851
|
|
|
837
852
|
addEdge (edge) {
|
|
@@ -852,10 +867,15 @@ class Shrinkwrap {
|
|
|
852
867
|
}
|
|
853
868
|
|
|
854
869
|
// we relativize the path here because that's how it shows up in the lock
|
|
855
|
-
// XXX
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
870
|
+
// XXX why is this different from pathFixed in this.add??
|
|
871
|
+
let pathFixed = null
|
|
872
|
+
if (node.resolved) {
|
|
873
|
+
if (!/file:/.test(node.resolved)) {
|
|
874
|
+
pathFixed = node.resolved
|
|
875
|
+
} else {
|
|
876
|
+
pathFixed = consistentResolve(node.resolved, node.path, this.path, true)
|
|
877
|
+
}
|
|
878
|
+
}
|
|
859
879
|
|
|
860
880
|
const spec = npa(`${node.name}@${edge.spec}`)
|
|
861
881
|
const entry = this.yarnLock.entries.get(`${node.name}@${edge.spec}`)
|
|
@@ -875,12 +895,12 @@ class Shrinkwrap {
|
|
|
875
895
|
node.resolved = node.resolved ||
|
|
876
896
|
consistentResolve(entry.resolved, this.path, node.path) || null
|
|
877
897
|
|
|
878
|
-
this
|
|
898
|
+
this.#awaitingUpdate.set(relpath(this.path, node.path), node)
|
|
879
899
|
}
|
|
880
900
|
|
|
881
|
-
|
|
882
|
-
const node = this
|
|
883
|
-
this
|
|
901
|
+
#updateWaitingNode (loc) {
|
|
902
|
+
const node = this.#awaitingUpdate.get(loc)
|
|
903
|
+
this.#awaitingUpdate.delete(loc)
|
|
884
904
|
this.data.packages[loc] = Shrinkwrap.metaFromNode(
|
|
885
905
|
node,
|
|
886
906
|
this.path,
|
|
@@ -911,9 +931,9 @@ class Shrinkwrap {
|
|
|
911
931
|
this.path,
|
|
912
932
|
this.resolveOptions)
|
|
913
933
|
}
|
|
914
|
-
} else if (this
|
|
915
|
-
for (const loc of this
|
|
916
|
-
this
|
|
934
|
+
} else if (this.#awaitingUpdate.size > 0) {
|
|
935
|
+
for (const loc of this.#awaitingUpdate.keys()) {
|
|
936
|
+
this.#updateWaitingNode(loc)
|
|
917
937
|
}
|
|
918
938
|
}
|
|
919
939
|
|
|
@@ -928,7 +948,7 @@ class Shrinkwrap {
|
|
|
928
948
|
delete this.data.packages['']
|
|
929
949
|
delete this.data.dependencies
|
|
930
950
|
} else if (this.tree && this.lockfileVersion <= 3) {
|
|
931
|
-
this
|
|
951
|
+
this.#buildLegacyLockfile(this.tree, this.data)
|
|
932
952
|
}
|
|
933
953
|
|
|
934
954
|
// lf version 1 = dependencies only
|
|
@@ -945,7 +965,7 @@ class Shrinkwrap {
|
|
|
945
965
|
}
|
|
946
966
|
}
|
|
947
967
|
|
|
948
|
-
|
|
968
|
+
#buildLegacyLockfile (node, lock, path = []) {
|
|
949
969
|
if (node === this.tree) {
|
|
950
970
|
// the root node
|
|
951
971
|
lock.name = node.packageName || node.name
|
|
@@ -966,9 +986,13 @@ class Shrinkwrap {
|
|
|
966
986
|
const aloc = a.from.location.split('node_modules')
|
|
967
987
|
const bloc = b.from.location.split('node_modules')
|
|
968
988
|
/* istanbul ignore next - sort calling order is indeterminate */
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
989
|
+
if (aloc.length > bloc.length) {
|
|
990
|
+
return 1
|
|
991
|
+
}
|
|
992
|
+
if (bloc.length > aloc.length) {
|
|
993
|
+
return -1
|
|
994
|
+
}
|
|
995
|
+
return localeCompare(aloc[aloc.length - 1], bloc[bloc.length - 1])
|
|
972
996
|
})[0]
|
|
973
997
|
|
|
974
998
|
const res = consistentResolve(node.resolved, this.path, this.path, true)
|
|
@@ -979,8 +1003,10 @@ class Shrinkwrap {
|
|
|
979
1003
|
// if we don't have either, just an empty object so nothing matches below.
|
|
980
1004
|
// This will effectively just save the version and resolved, as if it's
|
|
981
1005
|
// a standard version/range dep, which is a reasonable default.
|
|
982
|
-
|
|
983
|
-
|
|
1006
|
+
let spec = rSpec
|
|
1007
|
+
if (edge) {
|
|
1008
|
+
spec = npa.resolve(node.name, edge.spec, edge.from.realpath)
|
|
1009
|
+
}
|
|
984
1010
|
|
|
985
1011
|
if (node.isLink) {
|
|
986
1012
|
lock.version = `file:${relpath(this.path, node.realpath).replace(/#/g, '%23')}`
|
|
@@ -1086,7 +1112,7 @@ class Shrinkwrap {
|
|
|
1086
1112
|
if (path.includes(kid.realpath)) {
|
|
1087
1113
|
continue
|
|
1088
1114
|
}
|
|
1089
|
-
dependencies[name] = this
|
|
1115
|
+
dependencies[name] = this.#buildLegacyLockfile(kid, {}, kidPath)
|
|
1090
1116
|
found = true
|
|
1091
1117
|
}
|
|
1092
1118
|
if (found) {
|
package/lib/tree-check.js
CHANGED
|
@@ -90,7 +90,7 @@ const checkTree = (tree, checkUnreachable = true) => {
|
|
|
90
90
|
})
|
|
91
91
|
}
|
|
92
92
|
|
|
93
|
-
if (node.path === tree.root.path && node !== tree.root) {
|
|
93
|
+
if (node.path === tree.root.path && node !== tree.root && !tree.root.isLink) {
|
|
94
94
|
throw Object.assign(new Error('node with same path as root'), {
|
|
95
95
|
node: node.path,
|
|
96
96
|
tree: tree.path,
|
package/lib/yarn-lock.js
CHANGED
|
@@ -341,10 +341,10 @@ class YarnLock {
|
|
|
341
341
|
}
|
|
342
342
|
}
|
|
343
343
|
|
|
344
|
-
const _specs = Symbol('_specs')
|
|
345
344
|
class YarnLockEntry {
|
|
345
|
+
#specs
|
|
346
346
|
constructor (specs) {
|
|
347
|
-
this
|
|
347
|
+
this.#specs = new Set(specs)
|
|
348
348
|
this.resolved = null
|
|
349
349
|
this.version = null
|
|
350
350
|
this.integrity = null
|
|
@@ -354,7 +354,7 @@ class YarnLockEntry {
|
|
|
354
354
|
|
|
355
355
|
toString () {
|
|
356
356
|
// sort objects to the bottom, then alphabetical
|
|
357
|
-
return ([...this
|
|
357
|
+
return ([...this.#specs]
|
|
358
358
|
.sort(localeCompare)
|
|
359
359
|
.map(quoteIfNeeded).join(', ') +
|
|
360
360
|
':\n' +
|
|
@@ -370,7 +370,7 @@ class YarnLockEntry {
|
|
|
370
370
|
}
|
|
371
371
|
|
|
372
372
|
addSpec (spec) {
|
|
373
|
-
this
|
|
373
|
+
this.#specs.add(spec)
|
|
374
374
|
}
|
|
375
375
|
}
|
|
376
376
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@npmcli/arborist",
|
|
3
|
-
"version": "7.2.
|
|
3
|
+
"version": "7.2.2",
|
|
4
4
|
"description": "Manage node_modules trees",
|
|
5
5
|
"dependencies": {
|
|
6
6
|
"@isaacs/string-locale-compare": "^1.1.0",
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
"@npmcli/node-gyp": "^3.0.0",
|
|
13
13
|
"@npmcli/package-json": "^5.0.0",
|
|
14
14
|
"@npmcli/query": "^3.0.1",
|
|
15
|
-
"@npmcli/run-script": "^7.0.
|
|
15
|
+
"@npmcli/run-script": "^7.0.2",
|
|
16
16
|
"bin-links": "^4.0.1",
|
|
17
17
|
"cacache": "^18.0.0",
|
|
18
18
|
"common-ancestor-path": "^1.0.1",
|
|
@@ -39,7 +39,7 @@
|
|
|
39
39
|
},
|
|
40
40
|
"devDependencies": {
|
|
41
41
|
"@npmcli/eslint-config": "^4.0.0",
|
|
42
|
-
"@npmcli/template-oss": "4.
|
|
42
|
+
"@npmcli/template-oss": "4.21.3",
|
|
43
43
|
"benchmark": "^2.1.4",
|
|
44
44
|
"minify-registry-metadata": "^3.0.0",
|
|
45
45
|
"nock": "^13.3.3",
|
|
@@ -49,11 +49,11 @@
|
|
|
49
49
|
},
|
|
50
50
|
"scripts": {
|
|
51
51
|
"test": "tap",
|
|
52
|
-
"posttest": "
|
|
52
|
+
"posttest": "npm run lint",
|
|
53
53
|
"snap": "tap",
|
|
54
54
|
"test-proxy": "ARBORIST_TEST_PROXY=1 tap --snapshot",
|
|
55
|
-
"lint": "eslint \"**/*.js\"",
|
|
56
|
-
"lintfix": "
|
|
55
|
+
"lint": "eslint \"**/*.{js,cjs,ts,mjs,jsx,tsx}\"",
|
|
56
|
+
"lintfix": "npm run lint -- --fix",
|
|
57
57
|
"benchmark": "node scripts/benchmark.js",
|
|
58
58
|
"benchclean": "rm -rf scripts/benchmark/*/",
|
|
59
59
|
"postlint": "template-oss-check",
|
|
@@ -90,7 +90,7 @@
|
|
|
90
90
|
},
|
|
91
91
|
"templateOSS": {
|
|
92
92
|
"//@npmcli/template-oss": "This file is partially managed by @npmcli/template-oss. Edits may be overwritten.",
|
|
93
|
-
"version": "4.
|
|
93
|
+
"version": "4.21.3",
|
|
94
94
|
"content": "../../scripts/template-oss/index.js"
|
|
95
95
|
}
|
|
96
96
|
}
|