mirror of
https://github.com/nodejs/node.git
synced 2025-05-15 05:00:15 +00:00

PR-URL: https://github.com/nodejs/node/pull/44119 Reviewed-By: Ruy Adorno <ruyadorno@google.com> Reviewed-By: Mohammed Keyvanzadeh <mohammadkeyvanzade94@gmail.com> Reviewed-By: Tobias Nießen <tniessen@tnie.de> Reviewed-By: Luigi Pinca <luigipinca@gmail.com> Reviewed-By: Myles Borins <myles.borins@gmail.com>
145 lines
4.9 KiB
JavaScript
145 lines
4.9 KiB
JavaScript
// Do not rely on package._fields, so that we don't throw
|
|
// false failures if a tree is generated by other clients.
|
|
// Only relies on child.resolved, which MAY come from
|
|
// client-specific package.json meta _fields, but most of
|
|
// the time will be pulled out of a lockfile
|
|
|
|
const semver = require('semver')
|
|
const npa = require('npm-package-arg')
|
|
const { relative } = require('path')
|
|
const fromPath = require('./from-path.js')
|
|
|
|
const depValid = (child, requested, requestor) => {
|
|
// NB: we don't do much to verify 'tag' type requests.
|
|
// Just verify that we got a remote resolution. Presumably, it
|
|
// came from a registry and was tagged at some point.
|
|
|
|
if (typeof requested === 'string') {
|
|
try {
|
|
// tarball/dir must have resolved to the same tgz on disk, but for
|
|
// file: deps that depend on other files/dirs, we must resolve the
|
|
// location based on the *requestor* file/dir, not where it ends up.
|
|
// '' is equivalent to '*'
|
|
requested = npa.resolve(child.name, requested || '*', fromPath(requestor, requestor.edgesOut.get(child.name)))
|
|
} catch (er) {
|
|
// Not invalid because the child doesn't match, but because
|
|
// the spec itself is not supported. Nothing would match,
|
|
// so the edge is definitely not valid and never can be.
|
|
er.dependency = child.name
|
|
er.requested = requested
|
|
requestor.errors.push(er)
|
|
return false
|
|
}
|
|
}
|
|
|
|
// if the lockfile is super old, or hand-modified,
|
|
// then it's possible to hit this state.
|
|
if (!requested) {
|
|
const er = new Error('Invalid dependency specifier')
|
|
er.dependency = child.name
|
|
er.requested = requested
|
|
requestor.errors.push(er)
|
|
return false
|
|
}
|
|
|
|
switch (requested.type) {
|
|
case 'range':
|
|
if (requested.fetchSpec === '*') {
|
|
return true
|
|
}
|
|
// fallthrough
|
|
case 'version':
|
|
// if it's a version or a range other than '*', semver it
|
|
return semver.satisfies(child.version, requested.fetchSpec, true)
|
|
|
|
case 'directory':
|
|
return linkValid(child, requested, requestor)
|
|
|
|
case 'file':
|
|
return tarballValid(child, requested, requestor)
|
|
|
|
case 'alias':
|
|
// check that the alias target is valid
|
|
return depValid(child, requested.subSpec, requestor)
|
|
|
|
case 'tag':
|
|
// if it's a tag, we just verify that it has a tarball resolution
|
|
// presumably, it came from the registry and was tagged at some point
|
|
return child.resolved && npa(child.resolved).type === 'remote'
|
|
|
|
case 'remote':
|
|
// verify that we got it from the desired location
|
|
return child.resolved === requested.fetchSpec
|
|
|
|
case 'git': {
|
|
// if it's a git type, verify that they're the same repo
|
|
//
|
|
// if it specifies a definite commit, then it must have the
|
|
// same commit to be considered the same repo
|
|
//
|
|
// if it has a #semver:<range> specifier, verify that the
|
|
// version in the package is in the semver range
|
|
const resRepo = npa(child.resolved || '')
|
|
const resHost = resRepo.hosted
|
|
const reqHost = requested.hosted
|
|
const reqCommit = /^[a-fA-F0-9]{40}$/.test(requested.gitCommittish || '')
|
|
const nc = { noCommittish: !reqCommit }
|
|
const sameRepo =
|
|
resHost ? reqHost && reqHost.ssh(nc) === resHost.ssh(nc)
|
|
: resRepo.fetchSpec === requested.fetchSpec
|
|
|
|
return !sameRepo ? false
|
|
: !requested.gitRange ? true
|
|
: semver.satisfies(child.package.version, requested.gitRange, {
|
|
loose: true,
|
|
})
|
|
}
|
|
|
|
default: // unpossible, just being cautious
|
|
break
|
|
}
|
|
|
|
const er = new Error('Unsupported dependency type')
|
|
er.dependency = child.name
|
|
er.requested = requested
|
|
requestor.errors.push(er)
|
|
return false
|
|
}
|
|
|
|
const linkValid = (child, requested, requestor) => {
|
|
const isLink = !!child.isLink
|
|
// if we're installing links and the node is a link, then it's invalid because we want
|
|
// a real node to be there
|
|
if (requestor.installLinks) {
|
|
return !isLink
|
|
}
|
|
|
|
// directory must be a link to the specified folder
|
|
return isLink && relative(child.realpath, requested.fetchSpec) === ''
|
|
}
|
|
|
|
const tarballValid = (child, requested, requestor) => {
|
|
if (child.isLink) {
|
|
return false
|
|
}
|
|
|
|
if (child.resolved) {
|
|
return child.resolved.replace(/\\/g, '/') === `file:${requested.fetchSpec.replace(/\\/g, '/')}`
|
|
}
|
|
|
|
// if we have a legacy mutated package.json file. we can't be 100%
|
|
// sure that it resolved to the same file, but if it was the same
|
|
// request, that's a pretty good indicator of sameness.
|
|
if (child.package._requested) {
|
|
return child.package._requested.saveSpec === requested.saveSpec
|
|
}
|
|
|
|
// ok, we're probably dealing with some legacy cruft here, not much
|
|
// we can do at this point unfortunately.
|
|
return false
|
|
}
|
|
|
|
module.exports = (child, requested, accept, requestor) =>
|
|
depValid(child, requested, requestor) ||
|
|
(typeof accept === 'string' ? depValid(child, accept, requestor) : false)
|