1
1
'use strict'
2
-
module.exports = npa
3
-
module.exports.resolve = resolve
4
-
module.exports.toPurl = toPurl
5
-
module.exports.Result = Result
6
2
7
-
const { URL } = require('url')
3
+
const isWindows = process.platform === 'win32'
4
+
5
+
const { URL } = require('node:url')
6
+
// We need to use path/win32 so that we get consistent results in tests, but this also means we need to manually convert backslashes to forward slashes when generating file: urls with paths.
7
+
const path = isWindows ? require('node:path/win32') : require('node:path')
8
+
const { homedir } = require('node:os')
8
9
const HostedGit = require('hosted-git-info')
9
10
const semver = require('semver')
10
-
const path = global.FAKE_WINDOWS ? require('path').win32 : require('path')
11
11
const validatePackageName = require('validate-npm-package-name')
12
-
const { homedir } = require('os')
13
12
const { log } = require('proc-log')
14
13
15
-
const isWindows = process.platform === 'win32' || global.FAKE_WINDOWS
16
14
const hasSlashes = isWindows ? /\\|[/]/ : /[/]/
17
15
const isURL = /^(?:git[+])?[a-z]+:/i
18
16
const isGit = /^[^@]+@[^:.]+\.[^:]+:.+$/i
19
-
const isFilename = /[.](?:tgz|tar.gz|tar)$/i
17
+
const isFileType = /[.](?:tgz|tar.gz|tar)$/i
20
18
const isPortNumber = /:[0-9]+(\/|$)/i
19
+
const isWindowsFile = /^(?:[.]|~[/]|[/\\]|[a-zA-Z]:)/
20
+
const isPosixFile = /^(?:[.]|~[/]|[/]|[a-zA-Z]:)/
21
+
const defaultRegistry = 'https://registry.npmjs.org'
21
22
22
23
function npa (arg, where) {
23
24
let name
@@ -31,13 +32,14 @@ function npa (arg, where) {
31
32
return npa(arg.raw, where || arg.where)
32
33
}
33
34
}
34
-
const nameEndsAt = arg[0] === '@' ? arg.slice(1).indexOf('@') + 1 : arg.indexOf('@')
35
+
const nameEndsAt = arg.indexOf('@', 1) // Skip possible leading @
35
36
const namePart = nameEndsAt > 0 ? arg.slice(0, nameEndsAt) : arg
36
37
if (isURL.test(arg)) {
37
38
spec = arg
38
39
} else if (isGit.test(arg)) {
39
40
spec = `git+ssh://${arg}`
40
-
} else if (namePart[0] !== '@' && (hasSlashes.test(namePart) || isFilename.test(namePart))) {
41
+
// eslint-disable-next-line max-len
42
+
} else if (!namePart.startsWith('@') && (hasSlashes.test(namePart) || isFileType.test(namePart))) {
41
43
spec = arg
42
44
} else if (nameEndsAt > 0) {
43
45
name = namePart
@@ -54,7 +56,27 @@ function npa (arg, where) {
54
56
return resolve(name, spec, where, arg)
55
57
}
56
58
57
-
const isFilespec = isWindows ? /^(?:[.]|~[/]|[/\\]|[a-zA-Z]:)/ : /^(?:[.]|~[/]|[/]|[a-zA-Z]:)/
59
+
function isFileSpec (spec) {
60
+
if (!spec) {
61
+
return false
62
+
}
63
+
if (spec.toLowerCase().startsWith('file:')) {
64
+
return true
65
+
}
66
+
if (isWindows) {
67
+
return isWindowsFile.test(spec)
68
+
}
69
+
// We never hit this in windows tests, obviously
70
+
/* istanbul ignore next */
71
+
return isPosixFile.test(spec)
72
+
}
73
+
74
+
function isAliasSpec (spec) {
75
+
if (!spec) {
76
+
return false
77
+
}
78
+
return spec.toLowerCase().startsWith('npm:')
79
+
}
58
80
59
81
function resolve (name, spec, where, arg) {
60
82
const res = new Result({
@@ -65,12 +87,16 @@ function resolve (name, spec, where, arg) {
65
87
})
66
88
67
89
if (name) {
68
-
res.setName(name)
90
+
res.name = name
69
91
}
70
92
71
-
if (spec && (isFilespec.test(spec) || /^file:/i.test(spec))) {
93
+
if (!where) {
94
+
where = process.cwd()
95
+
}
96
+
97
+
if (isFileSpec(spec)) {
72
98
return fromFile(res, where)
73
-
} else if (spec && /^npm:/i.test(spec)) {
99
+
} else if (isAliasSpec(spec)) {
74
100
return fromAlias(res, where)
75
101
}
76
102
@@ -82,15 +108,13 @@ function resolve (name, spec, where, arg) {
82
108
return fromHostedGit(res, hosted)
83
109
} else if (spec && isURL.test(spec)) {
84
110
return fromURL(res)
85
-
} else if (spec && (hasSlashes.test(spec) || isFilename.test(spec))) {
111
+
} else if (spec && (hasSlashes.test(spec) || isFileType.test(spec))) {
86
112
return fromFile(res, where)
87
113
} else {
88
114
return fromRegistry(res)
89
115
}
90
116
}
91
117
92
-
const defaultRegistry = 'https://registry.npmjs.org'
93
-
94
118
function toPurl (arg, reg = defaultRegistry) {
95
119
const res = npa(arg)
96
120
@@ -128,60 +152,62 @@ function invalidPurlType (type, raw) {
128
152
return err
129
153
}
130
154
131
-
function Result (opts) {
132
-
this.type = opts.type
133
-
this.registry = opts.registry
134
-
this.where = opts.where
135
-
if (opts.raw == null) {
136
-
this.raw = opts.name ? opts.name + '@' + opts.rawSpec : opts.rawSpec
137
-
} else {
138
-
this.raw = opts.raw
155
+
class Result {
156
+
constructor (opts) {
157
+
this.type = opts.type
158
+
this.registry = opts.registry
159
+
this.where = opts.where
160
+
if (opts.raw == null) {
161
+
this.raw = opts.name ? `${opts.name}@${opts.rawSpec}` : opts.rawSpec
162
+
} else {
163
+
this.raw = opts.raw
164
+
}
165
+
this.name = undefined
166
+
this.escapedName = undefined
167
+
this.scope = undefined
168
+
this.rawSpec = opts.rawSpec || ''
169
+
this.saveSpec = opts.saveSpec
170
+
this.fetchSpec = opts.fetchSpec
171
+
if (opts.name) {
172
+
this.setName(opts.name)
173
+
}
174
+
this.gitRange = opts.gitRange
175
+
this.gitCommittish = opts.gitCommittish
176
+
this.gitSubdir = opts.gitSubdir
177
+
this.hosted = opts.hosted
139
178
}
140
179
141
-
this.name = undefined
142
-
this.escapedName = undefined
143
-
this.scope = undefined
144
-
this.rawSpec = opts.rawSpec || ''
145
-
this.saveSpec = opts.saveSpec
146
-
this.fetchSpec = opts.fetchSpec
147
-
if (opts.name) {
148
-
this.setName(opts.name)
149
-
}
150
-
this.gitRange = opts.gitRange
151
-
this.gitCommittish = opts.gitCommittish
152
-
this.gitSubdir = opts.gitSubdir
153
-
this.hosted = opts.hosted
154
-
}
180
+
// TODO move this to a getter/setter in a semver major
181
+
setName (name) {
182
+
const valid = validatePackageName(name)
183
+
if (!valid.validForOldPackages) {
184
+
throw invalidPackageName(name, valid, this.raw)
185
+
}
155
186
156
-
Result.prototype.setName = function (name) {
157
-
const valid = validatePackageName(name)
158
-
if (!valid.validForOldPackages) {
159
-
throw invalidPackageName(name, valid, this.raw)
187
+
this.name = name
188
+
this.scope = name[0] === '@' ? name.slice(0, name.indexOf('/')) : undefined
189
+
// scoped packages in couch must have slash url-encoded, e.g. @foo%2Fbar
190
+
this.escapedName = name.replace('/', '%2f')
191
+
return this
160
192
}
161
193
162
-
this.name = name
163
-
this.scope = name[0] === '@' ? name.slice(0, name.indexOf('/')) : undefined
164
-
// scoped packages in couch must have slash url-encoded, e.g. @foo%2Fbar
165
-
this.escapedName = name.replace('/', '%2f')
166
-
return this
167
-
}
168
-
169
-
Result.prototype.toString = function () {
170
-
const full = []
171
-
if (this.name != null && this.name !== '') {
172
-
full.push(this.name)
173
-
}
174
-
const spec = this.saveSpec || this.fetchSpec || this.rawSpec
175
-
if (spec != null && spec !== '') {
176
-
full.push(spec)
194
+
toString () {
195
+
const full = []
196
+
if (this.name != null && this.name !== '') {
197
+
full.push(this.name)
198
+
}
199
+
const spec = this.saveSpec || this.fetchSpec || this.rawSpec
200
+
if (spec != null && spec !== '') {
201
+
full.push(spec)
202
+
}
203
+
return full.length ? full.join('@') : this.raw
177
204
}
178
-
return full.length ? full.join('@') : this.raw
179
-
}
180
205
181
-
Result.prototype.toJSON = function () {
182
-
const result = Object.assign({}, this)
183
-
delete result.hosted
184
-
return result
206
+
toJSON () {
207
+
const result = Object.assign({}, this)
208
+
delete result.hosted
209
+
return result
210
+
}
185
211
}
186
212
187
213
// sets res.gitCommittish, res.gitRange, and res.gitSubdir
@@ -228,25 +254,67 @@ function setGitAttrs (res, committish) {
228
254
}
229
255
}
230
256
231
-
function fromFile (res, where) {
232
-
if (!where) {
233
-
where = process.cwd()
257
+
// Taken from: EncodePathChars and lookup_table in src/node_url.cc
258
+
// url.pathToFileURL only returns absolute references. We can't use it to encode paths.
259
+
// encodeURI mangles windows paths. We can't use it to encode paths.
260
+
// Under the hood, url.pathToFileURL does a limited set of encoding, with an extra windows step, and then calls path.resolve.
261
+
// The encoding node does without path.resolve is not available outside of the source, so we are recreating it here.
262
+
const encodedPathChars = new Map([
263
+
['\0', '%00'],
264
+
['\t', '%09'],
265
+
['\n', '%0A'],
266
+
['\r', '%0D'],
267
+
[' ', '%20'],
268
+
['"', '%22'],
269
+
['#', '%23'],
270
+
['%', '%25'],
271
+
['?', '%3F'],
272
+
['[', '%5B'],
273
+
['\\', isWindows ? '/' : '%5C'],
274
+
[']', '%5D'],
275
+
['^', '%5E'],
276
+
['|', '%7C'],
277
+
['~', '%7E'],
278
+
])
279
+
280
+
function pathToFileURL (str) {
281
+
let result = ''
282
+
for (let i = 0; i < str.length; i++) {
283
+
result = `${result}${encodedPathChars.get(str[i]) ?? str[i]}`
284
+
}
285
+
if (result.startsWith('file:')) {
286
+
return result
234
287
}
235
-
res.type = isFilename.test(res.rawSpec) ? 'file' : 'directory'
288
+
return `file:${result}`
289
+
}
290
+
291
+
function fromFile (res, where) {
292
+
res.type = isFileType.test(res.rawSpec) ? 'file' : 'directory'
236
293
res.where = where
237
294
238
-
// always put the '/' on where when resolving urls, or else
239
-
// file:foo from /path/to/bar goes to /path/to/foo, when we want
240
-
// it to be /path/to/bar/foo
295
+
let rawSpec = pathToFileURL(res.rawSpec)
296
+
297
+
if (rawSpec.startsWith('file:/')) {
298
+
// XXX backwards compatibility lack of compliance with RFC 8089
299
+
300
+
// turn file://path into file:/path
301
+
if (/^file:\/\/[^/]/.test(rawSpec)) {
302
+
rawSpec = `file:/${rawSpec.slice(5)}`
303
+
}
304
+
305
+
// turn file:/../path into file:../path
306
+
// for 1 or 3 leading slashes (2 is already ruled out from handling file:// explicitly above)
307
+
if (/^\/{1,3}\.\.?(\/|$)/.test(rawSpec.slice(5))) {
308
+
rawSpec = rawSpec.replace(/^file:\/{1,3}/, 'file:')
309
+
}
310
+
}
241
311
242
-
let specUrl
243
312
let resolvedUrl
244
-
const prefix = (!/^file:/.test(res.rawSpec) ? 'file:' : '')
245
-
const rawWithPrefix = prefix + res.rawSpec
246
-
let rawNoPrefix = rawWithPrefix.replace(/^file:/, '')
313
+
let specUrl
247
314
try {
248
-
resolvedUrl = new URL(rawWithPrefix, `file://${path.resolve(where)}/`)
249
-
specUrl = new URL(rawWithPrefix)
315
+
// always put the '/' on "where", or else file:foo from /path/to/bar goes to /path/to/foo, when we want it to be /path/to/bar/foo
316
+
resolvedUrl = new URL(rawSpec, `${pathToFileURL(path.resolve(where))}/`)
317
+
specUrl = new URL(rawSpec)
250
318
} catch (originalError) {
251
319
const er = new Error('Invalid file: URL, must comply with RFC 8089')
252
320
throw Object.assign(er, {
@@ -257,24 +325,6 @@ function fromFile (res, where) {
257
325
})
258
326
}
259
327
260
-
// XXX backwards compatibility lack of compliance with RFC 8089
261
-
if (resolvedUrl.host && resolvedUrl.host !== 'localhost') {
262
-
const rawSpec = res.rawSpec.replace(/^file:\/\//, 'file:///')
263
-
resolvedUrl = new URL(rawSpec, `file://${path.resolve(where)}/`)
264
-
specUrl = new URL(rawSpec)
265
-
rawNoPrefix = rawSpec.replace(/^file:/, '')
266
-
}
267
-
// turn file:/../foo into file:../foo
268
-
// for 1, 2 or 3 leading slashes since we attempted
269
-
// in the previous step to make it a file protocol url with a leading slash
270
-
if (/^\/{1,3}\.\.?(\/|$)/.test(rawNoPrefix)) {
271
-
const rawSpec = res.rawSpec.replace(/^file:\/{1,3}/, 'file:')
272
-
resolvedUrl = new URL(rawSpec, `file://${path.resolve(where)}/`)
273
-
specUrl = new URL(rawSpec)
274
-
rawNoPrefix = rawSpec.replace(/^file:/, '')
275
-
}
276
-
// XXX end RFC 8089 violation backwards compatibility section
277
-
278
328
// turn /C:/blah into just C:/blah on windows
279
329
let specPath = decodeURIComponent(specUrl.pathname)
280
330
let resolvedPath = decodeURIComponent(resolvedUrl.pathname)
@@ -288,13 +338,21 @@ function fromFile (res, where) {
288
338
if (/^\/~(\/|$)/.test(specPath)) {
289
339
res.saveSpec = `file:${specPath.substr(1)}`
290
340
resolvedPath = path.resolve(homedir(), specPath.substr(3))
291
-
} else if (!path.isAbsolute(rawNoPrefix)) {
341
+
} else if (!path.isAbsolute(rawSpec.slice(5))) {
292
342
res.saveSpec = `file:${path.relative(where, resolvedPath)}`
293
343
} else {
294
344
res.saveSpec = `file:${path.resolve(resolvedPath)}`
295
345
}
296
346
297
347
res.fetchSpec = path.resolve(where, resolvedPath)
348
+
// re-normalize the slashes in saveSpec due to node:path/win32 behavior in windows
349
+
res.saveSpec = res.saveSpec.split('\\').join('/')
350
+
// Ignoring because this only happens in windows
351
+
/* istanbul ignore next */
352
+
if (res.saveSpec.startsWith('file://')) {
353
+
// normalization of \\win32\root paths can cause a double / which we don't want
354
+
res.saveSpec = `file:/${res.saveSpec.slice(7)}`
355
+
}
298
356
return res
299
357
}
300
358
@@ -416,3 +474,8 @@ function fromRegistry (res) {
416
474
}
417
475
return res
418
476
}
477
+
478
+
module.exports = npa
479
+
module.exports.resolve = resolve
480
+
module.exports.toPurl = toPurl
481
+
module.exports.Result = Result
RetroSearch is an open source project built by @garambo | Open a GitHub Issue
Search and Browse the WWW like it's 1997 | Search results from DuckDuckGo
HTML:
3.2
| Encoding:
UTF-8
| Version:
0.7.4