• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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