source: trip-planner-front/node_modules/npm-package-arg/npa.js@ 6a3a178

Last change on this file since 6a3a178 was 6a3a178, checked in by Ema <ema_spirova@…>, 3 years ago

initial commit

  • Property mode set to 100644
File size: 10.7 KB
Line 
1'use strict'
2module.exports = npa
3module.exports.resolve = resolve
4module.exports.Result = Result
5
6const url = require('url')
7const HostedGit = require('hosted-git-info')
8const semver = require('semver')
9const path = global.FAKE_WINDOWS ? require('path').win32 : require('path')
10const validatePackageName = require('validate-npm-package-name')
11const { homedir } = require('os')
12
13const isWindows = process.platform === 'win32' || global.FAKE_WINDOWS
14const hasSlashes = isWindows ? /\\|[/]/ : /[/]/
15const isURL = /^(?:git[+])?[a-z]+:/i
16const isGit = /^[^@]+@[^:.]+\.[^:]+:.+$/i
17const isFilename = /[.](?:tgz|tar.gz|tar)$/i
18
19function npa (arg, where) {
20 let name
21 let spec
22 if (typeof arg === 'object') {
23 if (arg instanceof Result && (!where || where === arg.where))
24 return arg
25 else if (arg.name && arg.rawSpec)
26 return npa.resolve(arg.name, arg.rawSpec, where || arg.where)
27 else
28 return npa(arg.raw, where || arg.where)
29 }
30 const nameEndsAt = arg[0] === '@' ? arg.slice(1).indexOf('@') + 1 : arg.indexOf('@')
31 const namePart = nameEndsAt > 0 ? arg.slice(0, nameEndsAt) : arg
32 if (isURL.test(arg))
33 spec = arg
34 else if (isGit.test(arg))
35 spec = `git+ssh://${arg}`
36 else if (namePart[0] !== '@' && (hasSlashes.test(namePart) || isFilename.test(namePart)))
37 spec = arg
38 else if (nameEndsAt > 0) {
39 name = namePart
40 spec = arg.slice(nameEndsAt + 1)
41 } else {
42 const valid = validatePackageName(arg)
43 if (valid.validForOldPackages)
44 name = arg
45 else
46 spec = arg
47 }
48 return resolve(name, spec, where, arg)
49}
50
51const isFilespec = isWindows ? /^(?:[.]|~[/]|[/\\]|[a-zA-Z]:)/ : /^(?:[.]|~[/]|[/]|[a-zA-Z]:)/
52
53function resolve (name, spec, where, arg) {
54 const res = new Result({
55 raw: arg,
56 name: name,
57 rawSpec: spec,
58 fromArgument: arg != null,
59 })
60
61 if (name)
62 res.setName(name)
63
64 if (spec && (isFilespec.test(spec) || /^file:/i.test(spec)))
65 return fromFile(res, where)
66 else if (spec && /^npm:/i.test(spec))
67 return fromAlias(res, where)
68
69 const hosted = HostedGit.fromUrl(spec, {
70 noGitPlus: true,
71 noCommittish: true,
72 })
73 if (hosted)
74 return fromHostedGit(res, hosted)
75 else if (spec && isURL.test(spec))
76 return fromURL(res)
77 else if (spec && (hasSlashes.test(spec) || isFilename.test(spec)))
78 return fromFile(res, where)
79 else
80 return fromRegistry(res)
81}
82
83function invalidPackageName (name, valid) {
84 const err = new Error(`Invalid package name "${name}": ${valid.errors.join('; ')}`)
85 err.code = 'EINVALIDPACKAGENAME'
86 return err
87}
88function invalidTagName (name) {
89 const err = new Error(`Invalid tag name "${name}": Tags may not have any characters that encodeURIComponent encodes.`)
90 err.code = 'EINVALIDTAGNAME'
91 return err
92}
93
94function Result (opts) {
95 this.type = opts.type
96 this.registry = opts.registry
97 this.where = opts.where
98 if (opts.raw == null)
99 this.raw = opts.name ? opts.name + '@' + opts.rawSpec : opts.rawSpec
100 else
101 this.raw = opts.raw
102
103 this.name = undefined
104 this.escapedName = undefined
105 this.scope = undefined
106 this.rawSpec = opts.rawSpec == null ? '' : opts.rawSpec
107 this.saveSpec = opts.saveSpec
108 this.fetchSpec = opts.fetchSpec
109 if (opts.name)
110 this.setName(opts.name)
111 this.gitRange = opts.gitRange
112 this.gitCommittish = opts.gitCommittish
113 this.hosted = opts.hosted
114}
115
116Result.prototype.setName = function (name) {
117 const valid = validatePackageName(name)
118 if (!valid.validForOldPackages)
119 throw invalidPackageName(name, valid)
120
121 this.name = name
122 this.scope = name[0] === '@' ? name.slice(0, name.indexOf('/')) : undefined
123 // scoped packages in couch must have slash url-encoded, e.g. @foo%2Fbar
124 this.escapedName = name.replace('/', '%2f')
125 return this
126}
127
128Result.prototype.toString = function () {
129 const full = []
130 if (this.name != null && this.name !== '')
131 full.push(this.name)
132 const spec = this.saveSpec || this.fetchSpec || this.rawSpec
133 if (spec != null && spec !== '')
134 full.push(spec)
135 return full.length ? full.join('@') : this.raw
136}
137
138Result.prototype.toJSON = function () {
139 const result = Object.assign({}, this)
140 delete result.hosted
141 return result
142}
143
144function setGitCommittish (res, committish) {
145 if (committish != null && committish.length >= 7 && committish.slice(0, 7) === 'semver:') {
146 res.gitRange = decodeURIComponent(committish.slice(7))
147 res.gitCommittish = null
148 } else
149 res.gitCommittish = committish === '' ? null : committish
150
151 return res
152}
153
154function fromFile (res, where) {
155 if (!where)
156 where = process.cwd()
157 res.type = isFilename.test(res.rawSpec) ? 'file' : 'directory'
158 res.where = where
159
160 // always put the '/' on where when resolving urls, or else
161 // file:foo from /path/to/bar goes to /path/to/foo, when we want
162 // it to be /path/to/foo/bar
163
164 let specUrl
165 let resolvedUrl
166 const prefix = (!/^file:/.test(res.rawSpec) ? 'file:' : '')
167 const rawWithPrefix = prefix + res.rawSpec
168 let rawNoPrefix = rawWithPrefix.replace(/^file:/, '')
169 try {
170 resolvedUrl = new url.URL(rawWithPrefix, `file://${path.resolve(where)}/`)
171 specUrl = new url.URL(rawWithPrefix)
172 } catch (originalError) {
173 const er = new Error('Invalid file: URL, must comply with RFC 8909')
174 throw Object.assign(er, {
175 raw: res.rawSpec,
176 spec: res,
177 where,
178 originalError,
179 })
180 }
181
182 // environment switch for testing
183 if (process.env.NPM_PACKAGE_ARG_8909_STRICT !== '1') {
184 // XXX backwards compatibility lack of compliance with 8909
185 // Remove when we want a breaking change to come into RFC compliance.
186 if (resolvedUrl.host && resolvedUrl.host !== 'localhost') {
187 const rawSpec = res.rawSpec.replace(/^file:\/\//, 'file:///')
188 resolvedUrl = new url.URL(rawSpec, `file://${path.resolve(where)}/`)
189 specUrl = new url.URL(rawSpec)
190 rawNoPrefix = rawSpec.replace(/^file:/, '')
191 }
192 // turn file:/../foo into file:../foo
193 if (/^\/\.\.?(\/|$)/.test(rawNoPrefix)) {
194 const rawSpec = res.rawSpec.replace(/^file:\//, 'file:')
195 resolvedUrl = new url.URL(rawSpec, `file://${path.resolve(where)}/`)
196 specUrl = new url.URL(rawSpec)
197 rawNoPrefix = rawSpec.replace(/^file:/, '')
198 }
199 // XXX end 8909 violation backwards compatibility section
200 }
201
202 // file:foo - relative url to ./foo
203 // file:/foo - absolute path /foo
204 // file:///foo - absolute path to /foo, no authority host
205 // file://localhost/foo - absolute path to /foo, on localhost
206 // file://foo - absolute path to / on foo host (error!)
207 if (resolvedUrl.host && resolvedUrl.host !== 'localhost') {
208 const msg = `Invalid file: URL, must be absolute if // present`
209 throw Object.assign(new Error(msg), {
210 raw: res.rawSpec,
211 parsed: resolvedUrl,
212 })
213 }
214
215 // turn /C:/blah into just C:/blah on windows
216 let specPath = decodeURIComponent(specUrl.pathname)
217 let resolvedPath = decodeURIComponent(resolvedUrl.pathname)
218 if (isWindows) {
219 specPath = specPath.replace(/^\/+([a-z]:\/)/i, '$1')
220 resolvedPath = resolvedPath.replace(/^\/+([a-z]:\/)/i, '$1')
221 }
222
223 // replace ~ with homedir, but keep the ~ in the saveSpec
224 // otherwise, make it relative to where param
225 if (/^\/~(\/|$)/.test(specPath)) {
226 res.saveSpec = `file:${specPath.substr(1)}`
227 resolvedPath = path.resolve(homedir(), specPath.substr(3))
228 } else if (!path.isAbsolute(rawNoPrefix))
229 res.saveSpec = `file:${path.relative(where, resolvedPath)}`
230 else
231 res.saveSpec = `file:${path.resolve(resolvedPath)}`
232
233 res.fetchSpec = path.resolve(where, resolvedPath)
234 return res
235}
236
237function fromHostedGit (res, hosted) {
238 res.type = 'git'
239 res.hosted = hosted
240 res.saveSpec = hosted.toString({ noGitPlus: false, noCommittish: false })
241 res.fetchSpec = hosted.getDefaultRepresentation() === 'shortcut' ? null : hosted.toString()
242 return setGitCommittish(res, hosted.committish)
243}
244
245function unsupportedURLType (protocol, spec) {
246 const err = new Error(`Unsupported URL Type "${protocol}": ${spec}`)
247 err.code = 'EUNSUPPORTEDPROTOCOL'
248 return err
249}
250
251function matchGitScp (spec) {
252 // git ssh specifiers are overloaded to also use scp-style git
253 // specifiers, so we have to parse those out and treat them special.
254 // They are NOT true URIs, so we can't hand them to `url.parse`.
255 //
256 // This regex looks for things that look like:
257 // git+ssh://git@my.custom.git.com:username/project.git#deadbeef
258 //
259 // ...and various combinations. The username in the beginning is *required*.
260 const matched = spec.match(/^git\+ssh:\/\/([^:#]+:[^#]+(?:\.git)?)(?:#(.*))?$/i)
261 return matched && !matched[1].match(/:[0-9]+\/?.*$/i) && {
262 fetchSpec: matched[1],
263 gitCommittish: matched[2] == null ? null : matched[2],
264 }
265}
266
267function fromURL (res) {
268 // eslint-disable-next-line node/no-deprecated-api
269 const urlparse = url.parse(res.rawSpec)
270 res.saveSpec = res.rawSpec
271 // check the protocol, and then see if it's git or not
272 switch (urlparse.protocol) {
273 case 'git:':
274 case 'git+http:':
275 case 'git+https:':
276 case 'git+rsync:':
277 case 'git+ftp:':
278 case 'git+file:':
279 case 'git+ssh:': {
280 res.type = 'git'
281 const match = urlparse.protocol === 'git+ssh:' ? matchGitScp(res.rawSpec)
282 : null
283 if (match) {
284 setGitCommittish(res, match.gitCommittish)
285 res.fetchSpec = match.fetchSpec
286 } else {
287 setGitCommittish(res, urlparse.hash != null ? urlparse.hash.slice(1) : '')
288 urlparse.protocol = urlparse.protocol.replace(/^git[+]/, '')
289 if (urlparse.protocol === 'file:' && /^git\+file:\/\/[a-z]:/i.test(res.rawSpec)) {
290 // keep the drive letter : on windows file paths
291 urlparse.host += ':'
292 urlparse.hostname += ':'
293 }
294 delete urlparse.hash
295 res.fetchSpec = url.format(urlparse)
296 }
297 break
298 }
299 case 'http:':
300 case 'https:':
301 res.type = 'remote'
302 res.fetchSpec = res.saveSpec
303 break
304
305 default:
306 throw unsupportedURLType(urlparse.protocol, res.rawSpec)
307 }
308
309 return res
310}
311
312function fromAlias (res, where) {
313 const subSpec = npa(res.rawSpec.substr(4), where)
314 if (subSpec.type === 'alias')
315 throw new Error('nested aliases not supported')
316
317 if (!subSpec.registry)
318 throw new Error('aliases only work for registry deps')
319
320 res.subSpec = subSpec
321 res.registry = true
322 res.type = 'alias'
323 res.saveSpec = null
324 res.fetchSpec = null
325 return res
326}
327
328function fromRegistry (res) {
329 res.registry = true
330 const spec = res.rawSpec === '' ? 'latest' : res.rawSpec.trim()
331 // no save spec for registry components as we save based on the fetched
332 // version, not on the argument so this can't compute that.
333 res.saveSpec = null
334 res.fetchSpec = spec
335 const version = semver.valid(spec, true)
336 const range = semver.validRange(spec, true)
337 if (version)
338 res.type = 'version'
339 else if (range)
340 res.type = 'range'
341 else {
342 if (encodeURIComponent(spec) !== spec)
343 throw invalidTagName(spec)
344
345 res.type = 'tag'
346 }
347 return res
348}
Note: See TracBrowser for help on using the repository browser.