@npmcli/arborist
Advanced tools
Comparing version 7.2.1 to 7.2.2
@@ -336,3 +336,3 @@ // mix-in implementing the loadActual method | ||
const did = this.#actualTreeLoaded | ||
if (!did.has(node.target.realpath)) { | ||
if (!node.isLink && !did.has(node.target.realpath)) { | ||
did.add(node.target.realpath) | ||
@@ -339,0 +339,0 @@ await this.#loadFSChildren(node.target) |
@@ -51,3 +51,3 @@ // a module that manages a shrinkwrap file (npm-shrinkwrap.json or | ||
const npa = require('npm-package-arg') | ||
const rpj = require('read-package-json-fast') | ||
const pkgJson = require('@npmcli/package-json') | ||
const parseJSON = require('parse-conflict-json') | ||
@@ -85,24 +85,2 @@ | ||
const maybeReadFile = file => { | ||
return readFile(file, 'utf8').then(d => d, er => { | ||
/* istanbul ignore else - can't test without breaking module itself */ | ||
if (er.code === 'ENOENT') { | ||
return '' | ||
} else { | ||
throw er | ||
} | ||
}) | ||
} | ||
const maybeStatFile = file => { | ||
return stat(file).then(st => st.isFile(), er => { | ||
/* istanbul ignore else - can't test without breaking module itself */ | ||
if (er.code === 'ENOENT') { | ||
return null | ||
} else { | ||
throw er | ||
} | ||
}) | ||
} | ||
const pkgMetaKeys = [ | ||
@@ -139,42 +117,45 @@ // note: name is included if necessary, for alias packages | ||
const val = pkg[key] | ||
// get the license type, not an object | ||
return (key === 'license' && val && typeof val === 'object' && val.type) | ||
? val.type | ||
if (val) { | ||
// get only the license type, not the full object | ||
if (key === 'license' && typeof val === 'object' && val.type) { | ||
return val.type | ||
} | ||
// skip empty objects and falsey values | ||
: (val && !(typeof val === 'object' && !Object.keys(val).length)) ? val | ||
: null | ||
if (typeof val !== 'object' || Object.keys(val).length) { | ||
return val | ||
} | ||
} | ||
return null | ||
} | ||
// check to make sure that there are no packages newer than the hidden lockfile | ||
const assertNoNewer = async (path, data, lockTime, dir = path, seen = null) => { | ||
// check to make sure that there are no packages newer than or missing from the hidden lockfile | ||
const assertNoNewer = async (path, data, lockTime, dir, seen) => { | ||
const base = basename(dir) | ||
const isNM = dir !== path && base === 'node_modules' | ||
const isScope = dir !== path && !isNM && base.charAt(0) === '@' | ||
const isParent = dir === path || isNM || isScope | ||
const isScope = dir !== path && base.startsWith('@') | ||
const isParent = (dir === path) || isNM || isScope | ||
const parent = isParent ? dir : resolve(dir, 'node_modules') | ||
const rel = relpath(path, dir) | ||
if (dir !== path) { | ||
const dirTime = (await stat(dir)).mtime | ||
seen.add(rel) | ||
let entries | ||
if (dir === path) { | ||
entries = [{ name: 'node_modules', isDirectory: () => true }] | ||
} else { | ||
const { mtime: dirTime } = await stat(dir) | ||
if (dirTime > lockTime) { | ||
throw 'out of date, updated: ' + rel | ||
throw new Error(`out of date, updated: ${rel}`) | ||
} | ||
if (!isScope && !isNM && !data.packages[rel]) { | ||
throw 'missing from lockfile: ' + rel | ||
throw new Error(`missing from lockfile: ${rel}`) | ||
} | ||
seen.add(rel) | ||
} else { | ||
seen = new Set([rel]) | ||
entries = await readdir(parent, { withFileTypes: true }).catch(() => []) | ||
} | ||
const parent = isParent ? dir : resolve(dir, 'node_modules') | ||
const children = dir === path | ||
? Promise.resolve([{ name: 'node_modules', isDirectory: () => true }]) | ||
: readdir(parent, { withFileTypes: true }) | ||
const ents = await children.catch(() => []) | ||
await Promise.all(ents.map(async ent => { | ||
const child = resolve(parent, ent.name) | ||
if (ent.isDirectory() && !/^\./.test(ent.name)) { | ||
// TODO limit concurrency here, this is recursive | ||
await Promise.all(entries.map(async dirent => { | ||
const child = resolve(parent, dirent.name) | ||
if (dirent.isDirectory() && !dirent.name.startsWith('.')) { | ||
await assertNoNewer(path, data, lockTime, child, seen) | ||
} else if (ent.isSymbolicLink()) { | ||
} else if (dirent.isSymbolicLink()) { | ||
const target = resolve(parent, await readlink(child)) | ||
@@ -185,3 +166,3 @@ const tstat = await stat(target).catch( | ||
/* istanbul ignore next - windows cannot do this */ | ||
if (tstat && tstat.isDirectory() && !seen.has(relpath(path, target))) { | ||
if (tstat?.isDirectory() && !seen.has(relpath(path, target))) { | ||
await assertNoNewer(path, data, lockTime, target, seen) | ||
@@ -191,2 +172,3 @@ } | ||
})) | ||
if (dir !== path) { | ||
@@ -197,5 +179,5 @@ return | ||
// assert that all the entries in the lockfile were seen | ||
for (const loc of new Set(Object.keys(data.packages))) { | ||
for (const loc in data.packages) { | ||
if (!seen.has(loc)) { | ||
throw 'missing from node_modules: ' + loc | ||
throw new Error(`missing from node_modules: ${loc}`) | ||
} | ||
@@ -205,15 +187,2 @@ } | ||
const _awaitingUpdate = Symbol('_awaitingUpdate') | ||
const _updateWaitingNode = Symbol('_updateWaitingNode') | ||
const _lockFromLoc = Symbol('_lockFromLoc') | ||
const _pathToLoc = Symbol('_pathToLoc') | ||
const _loadAll = Symbol('_loadAll') | ||
const _metaFromLock = Symbol('_metaFromLock') | ||
const _resolveMetaNode = Symbol('_resolveMetaNode') | ||
const _fixDependencies = Symbol('_fixDependencies') | ||
const _buildLegacyLockfile = Symbol('_buildLegacyLockfile') | ||
const _filenameSet = Symbol('_filenameSet') | ||
const _maybeRead = Symbol('_maybeRead') | ||
const _maybeStat = Symbol('_maybeStat') | ||
class Shrinkwrap { | ||
@@ -238,9 +207,14 @@ static get defaultLockfileVersion () { | ||
const [sw, lock] = await s[_maybeStat]() | ||
const [sw, lock] = await s.resetFiles | ||
s.filename = resolve(s.path, | ||
(s.hiddenLockfile ? 'node_modules/.package-lock' | ||
: s.shrinkwrapOnly || sw ? 'npm-shrinkwrap' | ||
: 'package-lock') + '.json') | ||
// XXX this is duplicated in this.load(), but using loadFiles instead of resetFiles | ||
if (s.hiddenLockfile) { | ||
s.filename = resolve(s.path, 'node_modules/.package-lock.json') | ||
} else if (s.shrinkwrapOnly || sw) { | ||
s.filename = resolve(s.path, 'npm-shrinkwrap.json') | ||
} else { | ||
s.filename = resolve(s.path, 'package-lock.json') | ||
} | ||
s.loadedFromDisk = !!(sw || lock) | ||
// TODO what uses this? | ||
s.type = basename(s.filename) | ||
@@ -260,3 +234,3 @@ | ||
const meta = {} | ||
pkgMetaKeys.forEach(key => { | ||
for (const key of pkgMetaKeys) { | ||
const val = metaFieldFromPkg(node.package, key) | ||
@@ -266,3 +240,3 @@ if (val) { | ||
} | ||
}) | ||
} | ||
// we only include name if different from the node path name, and for the | ||
@@ -280,7 +254,7 @@ // root to help prevent churn based on the name of the directory the | ||
nodeMetaKeys.forEach(key => { | ||
for (const key of nodeMetaKeys) { | ||
if (node[key]) { | ||
meta[key] = node[key] | ||
} | ||
}) | ||
} | ||
@@ -316,2 +290,4 @@ const resolved = consistentResolve(node.resolved, node.path, path, true) | ||
#awaitingUpdate = new Map() | ||
constructor (options = {}) { | ||
@@ -328,7 +304,10 @@ const { | ||
this.lockfileVersion = hiddenLockfile ? 3 | ||
: lockfileVersion ? parseInt(lockfileVersion, 10) | ||
: null | ||
if (hiddenLockfile) { | ||
this.lockfileVersion = 3 | ||
} else if (lockfileVersion) { | ||
this.lockfileVersion = parseInt(lockfileVersion, 10) | ||
} else { | ||
this.lockfileVersion = null | ||
} | ||
this[_awaitingUpdate] = new Map() | ||
this.tree = null | ||
@@ -370,5 +349,8 @@ this.path = resolve(path || '.') | ||
const tgz = isReg && versionFromTgz(spec.name, resolved) || {} | ||
const yspec = tgz.name === spec.name && tgz.version === version ? version | ||
: isReg && tgz.name && tgz.version ? `npm:${tgz.name}@${tgz.version}` | ||
: resolved | ||
let yspec = resolved | ||
if (tgz.name === spec.name && tgz.version === version) { | ||
yspec = version | ||
} else if (isReg && tgz.name && tgz.version) { | ||
yspec = `npm:${tgz.name}@${tgz.version}` | ||
} | ||
if (yspec) { | ||
@@ -387,3 +369,3 @@ options.resolved = resolved.replace(yarnRegRe, 'https://registry.npmjs.org/') | ||
this.tree = null | ||
this[_awaitingUpdate] = new Map() | ||
this.#awaitingUpdate = new Map() | ||
const lockfileVersion = this.lockfileVersion || defaultLockfileVersion | ||
@@ -400,30 +382,47 @@ this.originalLockfileVersion = lockfileVersion | ||
[_filenameSet] () { | ||
return this.shrinkwrapOnly ? [ | ||
this.path + '/npm-shrinkwrap.json', | ||
] : this.hiddenLockfile ? [ | ||
null, | ||
this.path + '/node_modules/.package-lock.json', | ||
] : [ | ||
this.path + '/npm-shrinkwrap.json', | ||
this.path + '/package-lock.json', | ||
this.path + '/yarn.lock', | ||
// files to potentially read from and write to, in order of priority | ||
get #filenameSet () { | ||
if (this.shrinkwrapOnly) { | ||
return [`${this.path}/npm-shrinkwrap.json`] | ||
} | ||
if (this.hiddenLockfile) { | ||
return [`${this.path}/node_modules/.package-lock.json`] | ||
} | ||
return [ | ||
`${this.path}/npm-shrinkwrap.json`, | ||
`${this.path}/package-lock.json`, | ||
`${this.path}/yarn.lock`, | ||
] | ||
} | ||
[_maybeRead] () { | ||
return Promise.all(this[_filenameSet]().map(fn => fn && maybeReadFile(fn))) | ||
get loadFiles () { | ||
return Promise.all( | ||
this.#filenameSet.map(file => file && readFile(file, 'utf8').then(d => d, er => { | ||
/* istanbul ignore else - can't test without breaking module itself */ | ||
if (er.code === 'ENOENT') { | ||
return '' | ||
} else { | ||
throw er | ||
} | ||
})) | ||
) | ||
} | ||
[_maybeStat] () { | ||
// throw away yarn, we only care about lock or shrinkwrap when checking | ||
get resetFiles () { | ||
// slice out yarn, we only care about lock or shrinkwrap when checking | ||
// this way, since we're not actually loading the full lock metadata | ||
return Promise.all(this[_filenameSet]().slice(0, 2) | ||
.map(fn => fn && maybeStatFile(fn))) | ||
return Promise.all(this.#filenameSet.slice(0, 2) | ||
.map(file => file && stat(file).then(st => st.isFile(), er => { | ||
/* istanbul ignore else - can't test without breaking module itself */ | ||
if (er.code === 'ENOENT') { | ||
return null | ||
} else { | ||
throw er | ||
} | ||
}) | ||
) | ||
) | ||
} | ||
inferFormattingOptions (packageJSONData) { | ||
// don't use detect-indent, just pick the first line. | ||
// if the file starts with {" then we have an indent of '', ie, none | ||
// which will default to 2 at save time. | ||
const { | ||
@@ -433,4 +432,8 @@ [Symbol.for('indent')]: indent, | ||
} = packageJSONData | ||
this.indent = indent !== undefined ? indent : this.indent | ||
this.newline = newline !== undefined ? newline : this.newline | ||
if (indent !== undefined) { | ||
this.indent = indent | ||
} | ||
if (newline !== undefined) { | ||
this.newline = newline | ||
} | ||
} | ||
@@ -441,4 +444,6 @@ | ||
// only npm-shrinkwrap.json. | ||
return this[_maybeRead]().then(([sw, lock, yarn]) => { | ||
const data = sw || lock || '' | ||
let data | ||
try { | ||
const [sw, lock, yarn] = await this.loadFiles | ||
data = sw || lock || '{}' | ||
@@ -448,9 +453,11 @@ // use shrinkwrap only for deps, otherwise prefer package-lock | ||
// TODO: emit a warning here or something if both are present. | ||
this.filename = resolve(this.path, | ||
(this.hiddenLockfile ? 'node_modules/.package-lock' | ||
: this.shrinkwrapOnly || sw ? 'npm-shrinkwrap' | ||
: 'package-lock') + '.json') | ||
if (this.hiddenLockfile) { | ||
this.filename = resolve(this.path, 'node_modules/.package-lock.json') | ||
} else if (this.shrinkwrapOnly || sw) { | ||
this.filename = resolve(this.path, 'npm-shrinkwrap.json') | ||
} else { | ||
this.filename = resolve(this.path, 'package-lock.json') | ||
} | ||
this.type = basename(this.filename) | ||
this.loadedFromDisk = !!data | ||
this.loadedFromDisk = Boolean(sw || lock) | ||
@@ -467,23 +474,19 @@ if (yarn) { | ||
return data ? parseJSON(data) : {} | ||
}).then(async data => { | ||
data = parseJSON(data) | ||
this.inferFormattingOptions(data) | ||
if (!this.hiddenLockfile || !data.packages) { | ||
return data | ||
if (this.hiddenLockfile && data.packages) { | ||
// add a few ms just to account for jitter | ||
const lockTime = +(await stat(this.filename)).mtime + 10 | ||
await assertNoNewer(this.path, data, lockTime, this.path, new Set()) | ||
} | ||
// add a few ms just to account for jitter | ||
const lockTime = +(await stat(this.filename)).mtime + 10 | ||
await assertNoNewer(this.path, data, lockTime) | ||
// all good! hidden lockfile is the newest thing in here. | ||
return data | ||
}).catch(er => { | ||
} catch (er) { | ||
/* istanbul ignore else */ | ||
if (typeof this.filename === 'string') { | ||
const rel = relpath(this.path, this.filename) | ||
log.verbose('shrinkwrap', `failed to load ${rel}`, er) | ||
log.verbose('shrinkwrap', `failed to load ${rel}`, er.message) | ||
} else { | ||
log.verbose('shrinkwrap', `failed to load ${this.path}`, er) | ||
log.verbose('shrinkwrap', `failed to load ${this.path}`, er.message) | ||
} | ||
@@ -493,48 +496,51 @@ this.loadingError = er | ||
this.ancientLockfile = false | ||
return {} | ||
}).then(lock => { | ||
// auto convert v1 lockfiles to v3 | ||
// leave v2 in place unless configured | ||
// v3 by default | ||
const lockfileVersion = | ||
this.lockfileVersion ? this.lockfileVersion | ||
: lock.lockfileVersion === 1 ? defaultLockfileVersion | ||
: lock.lockfileVersion || defaultLockfileVersion | ||
data = {} | ||
} | ||
// auto convert v1 lockfiles to v3 | ||
// leave v2 in place unless configured | ||
// v3 by default | ||
let lockfileVersion = defaultLockfileVersion | ||
if (this.lockfileVersion) { | ||
lockfileVersion = this.lockfileVersion | ||
} else if (data.lockfileVersion && data.lockfileVersion !== 1) { | ||
lockfileVersion = data.lockfileVersion | ||
} | ||
this.data = { | ||
...lock, | ||
lockfileVersion: lockfileVersion, | ||
requires: true, | ||
packages: lock.packages || {}, | ||
dependencies: lock.dependencies || {}, | ||
} | ||
this.data = { | ||
...data, | ||
lockfileVersion, | ||
requires: true, | ||
packages: data.packages || {}, | ||
dependencies: data.dependencies || {}, | ||
} | ||
this.originalLockfileVersion = lock.lockfileVersion | ||
this.originalLockfileVersion = data.lockfileVersion | ||
// use default if it wasn't explicitly set, and the current file is | ||
// less than our default. otherwise, keep whatever is in the file, | ||
// unless we had an explicit setting already. | ||
if (!this.lockfileVersion) { | ||
this.lockfileVersion = this.data.lockfileVersion = lockfileVersion | ||
} | ||
this.ancientLockfile = this.loadedFromDisk && | ||
!(lock.lockfileVersion >= 2) && !lock.requires | ||
// use default if it wasn't explicitly set, and the current file is | ||
// less than our default. otherwise, keep whatever is in the file, | ||
// unless we had an explicit setting already. | ||
if (!this.lockfileVersion) { | ||
this.lockfileVersion = this.data.lockfileVersion = lockfileVersion | ||
} | ||
this.ancientLockfile = this.loadedFromDisk && | ||
!(data.lockfileVersion >= 2) && !data.requires | ||
// load old lockfile deps into the packages listing | ||
// eslint-disable-next-line promise/always-return | ||
if (lock.dependencies && !lock.packages) { | ||
return rpj(this.path + '/package.json').then(pkg => pkg, er => ({})) | ||
// eslint-disable-next-line promise/always-return | ||
.then(pkg => { | ||
this[_loadAll]('', null, this.data) | ||
this[_fixDependencies](pkg) | ||
}) | ||
// load old lockfile deps into the packages listing | ||
if (data.dependencies && !data.packages) { | ||
let pkg | ||
try { | ||
pkg = await pkgJson.normalize(this.path) | ||
pkg = pkg.content | ||
} catch { | ||
pkg = {} | ||
} | ||
}) | ||
.then(() => this) | ||
this.#loadAll('', null, this.data) | ||
this.#fixDependencies(pkg) | ||
} | ||
return this | ||
} | ||
[_loadAll] (location, name, lock) { | ||
#loadAll (location, name, lock) { | ||
// migrate a v1 package lock to the new format. | ||
const meta = this[_metaFromLock](location, name, lock) | ||
const meta = this.#metaFromLock(location, name, lock) | ||
// dependencies nested under a link are actually under the link target | ||
@@ -545,5 +551,5 @@ if (meta.link) { | ||
if (lock.dependencies) { | ||
for (const [name, dep] of Object.entries(lock.dependencies)) { | ||
for (const name in lock.dependencies) { | ||
const loc = location + (location ? '/' : '') + 'node_modules/' + name | ||
this[_loadAll](loc, name, dep) | ||
this.#loadAll(loc, name, lock.dependencies[name]) | ||
} | ||
@@ -556,3 +562,3 @@ } | ||
// to correct that now, or every link will be considered prod | ||
[_fixDependencies] (pkg) { | ||
#fixDependencies (pkg) { | ||
// we need the root package.json because legacy shrinkwraps just | ||
@@ -562,11 +568,11 @@ // have requires:true at the root level, which is even less useful | ||
const root = this.data.packages[''] | ||
pkgMetaKeys.forEach(key => { | ||
for (const key of pkgMetaKeys) { | ||
const val = metaFieldFromPkg(pkg, key) | ||
const k = key.replace(/^_/, '') | ||
if (val) { | ||
root[k] = val | ||
root[key.replace(/^_/, '')] = val | ||
} | ||
}) | ||
} | ||
for (const [loc, meta] of Object.entries(this.data.packages)) { | ||
for (const loc in this.data.packages) { | ||
const meta = this.data.packages[loc] | ||
if (!meta.requires || !loc) { | ||
@@ -582,15 +588,20 @@ continue | ||
// buildIdealTree process | ||
for (const [name, spec] of Object.entries(meta.requires)) { | ||
const dep = this[_resolveMetaNode](loc, name) | ||
for (const name in meta.requires) { | ||
const dep = this.#resolveMetaNode(loc, name) | ||
// this overwrites the false value set above | ||
const depType = dep && dep.optional && !meta.optional | ||
? 'optionalDependencies' | ||
: /* istanbul ignore next - dev deps are only for the root level */ | ||
dep && dep.dev && !meta.dev ? 'devDependencies' | ||
// also land here if the dep just isn't in the tree, which maybe | ||
// should be an error, since it means that the shrinkwrap is | ||
// invalid, but we can't do much better without any info. | ||
: 'dependencies' | ||
meta[depType] = meta[depType] || {} | ||
meta[depType][name] = spec | ||
// default to dependencies if the dep just isn't in the tree, which | ||
// maybe should be an error, since it means that the shrinkwrap is | ||
// invalid, but we can't do much better without any info. | ||
let depType = 'dependencies' | ||
/* istanbul ignore else - dev deps are only for the root level */ | ||
if (dep?.optional && !meta.optional) { | ||
depType = 'optionalDependencies' | ||
} else if (dep?.dev && !meta.dev) { | ||
// XXX is this even reachable? | ||
depType = 'devDependencies' | ||
} | ||
if (!meta[depType]) { | ||
meta[depType] = {} | ||
} | ||
meta[depType][name] = meta.requires[name] | ||
} | ||
@@ -601,3 +612,3 @@ delete meta.requires | ||
[_resolveMetaNode] (loc, name) { | ||
#resolveMetaNode (loc, name) { | ||
for (let path = loc; true; path = path.replace(/(^|\/)[^/]*$/, '')) { | ||
@@ -616,3 +627,3 @@ const check = `${path}${path ? '/' : ''}node_modules/${name}` | ||
[_lockFromLoc] (lock, path, i = 0) { | ||
#lockFromLoc (lock, path, i = 0) { | ||
if (!lock) { | ||
@@ -634,3 +645,3 @@ return null | ||
return this[_lockFromLoc](lock.dependencies[path[i]], path, i + 1) | ||
return this.#lockFromLoc(lock.dependencies[path[i]], path, i + 1) | ||
} | ||
@@ -640,3 +651,3 @@ | ||
// get back a /-normalized location based on root path. | ||
[_pathToLoc] (path) { | ||
#pathToLoc (path) { | ||
return relpath(this.path, resolve(this.path, path)) | ||
@@ -649,4 +660,4 @@ } | ||
} | ||
const location = this[_pathToLoc](nodePath) | ||
this[_awaitingUpdate].delete(location) | ||
const location = this.#pathToLoc(nodePath) | ||
this.#awaitingUpdate.delete(location) | ||
@@ -656,3 +667,3 @@ delete this.data.packages[location] | ||
const name = path.pop() | ||
const pLock = this[_lockFromLoc](this.data, path) | ||
const pLock = this.#lockFromLoc(this.data, path) | ||
if (pLock && pLock.dependencies) { | ||
@@ -668,5 +679,5 @@ delete pLock.dependencies[name] | ||
const location = this[_pathToLoc](nodePath) | ||
if (this[_awaitingUpdate].has(location)) { | ||
this[_updateWaitingNode](location) | ||
const location = this.#pathToLoc(nodePath) | ||
if (this.#awaitingUpdate.has(location)) { | ||
this.#updateWaitingNode(location) | ||
} | ||
@@ -684,8 +695,8 @@ | ||
const name = path[path.length - 1] | ||
const lock = this[_lockFromLoc](this.data, path) | ||
const lock = this.#lockFromLoc(this.data, path) | ||
return this[_metaFromLock](location, name, lock) | ||
return this.#metaFromLock(location, name, lock) | ||
} | ||
[_metaFromLock] (location, name, lock) { | ||
#metaFromLock (location, name, lock) { | ||
// This function tries as hard as it can to figure out the metadata | ||
@@ -715,3 +726,3 @@ // from a lockfile which may be outdated or incomplete. Since v1 | ||
if (!this.data.packages[target]) { | ||
this[_metaFromLock](target, name, { ...lock, version: null }) | ||
this.#metaFromLock(target, name, { ...lock, version: null }) | ||
} | ||
@@ -836,6 +847,10 @@ return this.data.packages[location] | ||
const pathFixed = !resolved ? null | ||
: !/^file:/.test(resolved) ? resolved | ||
// resolve onto the metadata path | ||
: `file:${resolve(this.path, resolved.slice(5)).replace(/#/g, '%23')}` | ||
let pathFixed = null | ||
if (resolved) { | ||
if (!/^file:/.test(resolved)) { | ||
pathFixed = resolved | ||
} else { | ||
pathFixed = `file:${resolve(this.path, resolved.slice(5)).replace(/#/g, '%23')}` | ||
} | ||
} | ||
@@ -869,3 +884,3 @@ // if we have one, only set the other if it matches | ||
} | ||
this[_awaitingUpdate].set(loc, node) | ||
this.#awaitingUpdate.set(loc, node) | ||
} | ||
@@ -891,6 +906,11 @@ | ||
// we relativize the path here because that's how it shows up in the lock | ||
// XXX how is this different from pathFixed above?? | ||
const pathFixed = !node.resolved ? null | ||
: !/file:/.test(node.resolved) ? node.resolved | ||
: consistentResolve(node.resolved, node.path, this.path, true) | ||
// XXX why is this different from pathFixed in this.add?? | ||
let pathFixed = null | ||
if (node.resolved) { | ||
if (!/file:/.test(node.resolved)) { | ||
pathFixed = node.resolved | ||
} else { | ||
pathFixed = consistentResolve(node.resolved, node.path, this.path, true) | ||
} | ||
} | ||
@@ -915,8 +935,8 @@ const spec = npa(`${node.name}@${edge.spec}`) | ||
this[_awaitingUpdate].set(relpath(this.path, node.path), node) | ||
this.#awaitingUpdate.set(relpath(this.path, node.path), node) | ||
} | ||
[_updateWaitingNode] (loc) { | ||
const node = this[_awaitingUpdate].get(loc) | ||
this[_awaitingUpdate].delete(loc) | ||
#updateWaitingNode (loc) { | ||
const node = this.#awaitingUpdate.get(loc) | ||
this.#awaitingUpdate.delete(loc) | ||
this.data.packages[loc] = Shrinkwrap.metaFromNode( | ||
@@ -952,5 +972,5 @@ node, | ||
} | ||
} else if (this[_awaitingUpdate].size > 0) { | ||
for (const loc of this[_awaitingUpdate].keys()) { | ||
this[_updateWaitingNode](loc) | ||
} else if (this.#awaitingUpdate.size > 0) { | ||
for (const loc of this.#awaitingUpdate.keys()) { | ||
this.#updateWaitingNode(loc) | ||
} | ||
@@ -970,3 +990,3 @@ } | ||
} else if (this.tree && this.lockfileVersion <= 3) { | ||
this[_buildLegacyLockfile](this.tree, this.data) | ||
this.#buildLegacyLockfile(this.tree, this.data) | ||
} | ||
@@ -988,3 +1008,3 @@ | ||
[_buildLegacyLockfile] (node, lock, path = []) { | ||
#buildLegacyLockfile (node, lock, path = []) { | ||
if (node === this.tree) { | ||
@@ -1010,5 +1030,9 @@ // the root node | ||
/* istanbul ignore next - sort calling order is indeterminate */ | ||
return aloc.length > bloc.length ? 1 | ||
: bloc.length > aloc.length ? -1 | ||
: localeCompare(aloc[aloc.length - 1], bloc[bloc.length - 1]) | ||
if (aloc.length > bloc.length) { | ||
return 1 | ||
} | ||
if (bloc.length > aloc.length) { | ||
return -1 | ||
} | ||
return localeCompare(aloc[aloc.length - 1], bloc[bloc.length - 1]) | ||
})[0] | ||
@@ -1024,4 +1048,6 @@ | ||
// a standard version/range dep, which is a reasonable default. | ||
const spec = !edge ? rSpec | ||
: npa.resolve(node.name, edge.spec, edge.from.realpath) | ||
let spec = rSpec | ||
if (edge) { | ||
spec = npa.resolve(node.name, edge.spec, edge.from.realpath) | ||
} | ||
@@ -1132,3 +1158,3 @@ if (node.isLink) { | ||
} | ||
dependencies[name] = this[_buildLegacyLockfile](kid, {}, kidPath) | ||
dependencies[name] = this.#buildLegacyLockfile(kid, {}, kidPath) | ||
found = true | ||
@@ -1135,0 +1161,0 @@ } |
@@ -93,3 +93,3 @@ const debug = require('./debug.js') | ||
if (node.path === tree.root.path && node !== tree.root) { | ||
if (node.path === tree.root.path && node !== tree.root && !tree.root.isLink) { | ||
throw Object.assign(new Error('node with same path as root'), { | ||
@@ -96,0 +96,0 @@ node: node.path, |
@@ -344,6 +344,6 @@ // parse a yarn lock file | ||
const _specs = Symbol('_specs') | ||
class YarnLockEntry { | ||
#specs | ||
constructor (specs) { | ||
this[_specs] = new Set(specs) | ||
this.#specs = new Set(specs) | ||
this.resolved = null | ||
@@ -358,3 +358,3 @@ this.version = null | ||
// sort objects to the bottom, then alphabetical | ||
return ([...this[_specs]] | ||
return ([...this.#specs] | ||
.sort(localeCompare) | ||
@@ -375,3 +375,3 @@ .map(quoteIfNeeded).join(', ') + | ||
addSpec (spec) { | ||
this[_specs].add(spec) | ||
this.#specs.add(spec) | ||
} | ||
@@ -378,0 +378,0 @@ } |
{ | ||
"name": "@npmcli/arborist", | ||
"version": "7.2.1", | ||
"version": "7.2.2", | ||
"description": "Manage node_modules trees", | ||
@@ -42,3 +42,3 @@ "dependencies": { | ||
"@npmcli/eslint-config": "^4.0.0", | ||
"@npmcli/template-oss": "4.19.0", | ||
"@npmcli/template-oss": "4.21.3", | ||
"benchmark": "^2.1.4", | ||
@@ -53,7 +53,7 @@ "minify-registry-metadata": "^3.0.0", | ||
"test": "tap", | ||
"posttest": "node ../.. run lint", | ||
"posttest": "npm run lint", | ||
"snap": "tap", | ||
"test-proxy": "ARBORIST_TEST_PROXY=1 tap --snapshot", | ||
"lint": "eslint \"**/*.js\"", | ||
"lintfix": "node ../.. run lint -- --fix", | ||
"lint": "eslint \"**/*.{js,cjs,ts,mjs,jsx,tsx}\"", | ||
"lintfix": "npm run lint -- --fix", | ||
"benchmark": "node scripts/benchmark.js", | ||
@@ -95,5 +95,5 @@ "benchclean": "rm -rf scripts/benchmark/*/", | ||
"//@npmcli/template-oss": "This file is partially managed by @npmcli/template-oss. Edits may be overwritten.", | ||
"version": "4.19.0", | ||
"version": "4.21.3", | ||
"content": "../../scripts/template-oss/index.js" | ||
} | ||
} |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
12245
459202