1'use strict' 2 3const figgyPudding = require('figgy-pudding') 4const npa = require('npm-package-arg') 5const semver = require('semver') 6 7const PickerOpts = figgyPudding({ 8 defaultTag: { default: 'latest' }, 9 enjoyBy: {}, 10 includeDeprecated: { default: false } 11}) 12 13module.exports = pickManifest 14function pickManifest (packument, wanted, opts) { 15 opts = PickerOpts(opts) 16 const time = opts.enjoyBy && packument.time && +(new Date(opts.enjoyBy)) 17 const spec = npa.resolve(packument.name, wanted) 18 const type = spec.type 19 if (type === 'version' || type === 'range') { 20 wanted = semver.clean(wanted, true) || wanted 21 } 22 const distTags = packument['dist-tags'] || {} 23 const versions = Object.keys(packument.versions || {}).filter(v => { 24 return semver.valid(v, true) 25 }) 26 const policyRestrictions = packument.policyRestrictions 27 const restrictedVersions = policyRestrictions 28 ? Object.keys(policyRestrictions.versions) : [] 29 30 function enjoyableBy (v) { 31 return !time || ( 32 packument.time[v] && time >= +(new Date(packument.time[v])) 33 ) 34 } 35 36 let err 37 38 if (!versions.length && !restrictedVersions.length) { 39 err = new Error(`No valid versions available for ${packument.name}`) 40 err.code = 'ENOVERSIONS' 41 err.name = packument.name 42 err.type = type 43 err.wanted = wanted 44 throw err 45 } 46 47 let target 48 49 if (type === 'tag' && enjoyableBy(distTags[wanted])) { 50 target = distTags[wanted] 51 } else if (type === 'version') { 52 target = wanted 53 } else if (type !== 'range' && enjoyableBy(distTags[wanted])) { 54 throw new Error('Only tag, version, and range are supported') 55 } 56 57 const tagVersion = distTags[opts.defaultTag] 58 59 if ( 60 !target && 61 tagVersion && 62 packument.versions[tagVersion] && 63 enjoyableBy(tagVersion) && 64 semver.satisfies(tagVersion, wanted, true) 65 ) { 66 target = tagVersion 67 } 68 69 if (!target && !opts.includeDeprecated) { 70 const undeprecated = versions.filter(v => !packument.versions[v].deprecated && enjoyableBy(v) 71 ) 72 target = semver.maxSatisfying(undeprecated, wanted, true) 73 } 74 if (!target) { 75 const stillFresh = versions.filter(enjoyableBy) 76 target = semver.maxSatisfying(stillFresh, wanted, true) 77 } 78 79 if (!target && wanted === '*' && enjoyableBy(tagVersion)) { 80 // This specific corner is meant for the case where 81 // someone is using `*` as a selector, but all versions 82 // are pre-releases, which don't match ranges at all. 83 target = tagVersion 84 } 85 86 if ( 87 !target && 88 time && 89 type === 'tag' && 90 distTags[wanted] && 91 !enjoyableBy(distTags[wanted]) 92 ) { 93 const stillFresh = versions.filter(v => 94 enjoyableBy(v) && semver.lte(v, distTags[wanted], true) 95 ).sort(semver.rcompare) 96 target = stillFresh[0] 97 } 98 99 if (!target && restrictedVersions) { 100 target = semver.maxSatisfying(restrictedVersions, wanted, true) 101 } 102 103 const manifest = ( 104 target && 105 packument.versions[target] 106 ) 107 if (!manifest) { 108 // Check if target is forbidden 109 const isForbidden = target && policyRestrictions && policyRestrictions.versions[target] 110 const pckg = `${packument.name}@${wanted}${ 111 opts.enjoyBy 112 ? ` with an Enjoy By date of ${ 113 new Date(opts.enjoyBy).toLocaleString() 114 }. Maybe try a different date?` 115 : '' 116 }` 117 118 if (isForbidden) { 119 err = new Error(`Could not download ${pckg} due to policy violations.\n${policyRestrictions.message}\n`) 120 err.code = 'E403' 121 } else { 122 err = new Error(`No matching version found for ${pckg}.`) 123 err.code = 'ETARGET' 124 } 125 126 err.name = packument.name 127 err.type = type 128 err.wanted = wanted 129 err.versions = versions 130 err.distTags = distTags 131 err.defaultTag = opts.defaultTag 132 throw err 133 } else { 134 return manifest 135 } 136} 137