Skip to content

Commit b153927

Browse files
committed
deps: npm-package-arg@12.0.2
1 parent 3a80a7b commit b153927

File tree

4 files changed

+168
-105
lines changed

4 files changed

+168
-105
lines changed

‎node_modules/npm-package-arg/lib/npa.js

+160-97
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,24 @@
11
'use strict'
2-
module.exports = npa
3-
module.exports.resolve = resolve
4-
module.exports.toPurl = toPurl
5-
module.exports.Result = Result
62

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')
89
const HostedGit = require('hosted-git-info')
910
const semver = require('semver')
10-
const path = global.FAKE_WINDOWS ? require('path').win32 : require('path')
1111
const validatePackageName = require('validate-npm-package-name')
12-
const { homedir } = require('os')
1312
const { log } = require('proc-log')
1413

15-
const isWindows = process.platform === 'win32' || global.FAKE_WINDOWS
1614
const hasSlashes = isWindows ? /\\|[/]/ : /[/]/
1715
const isURL = /^(?:git[+])?[a-z]+:/i
1816
const isGit = /^[^@]+@[^:.]+\.[^:]+:.+$/i
19-
const isFilename = /[.](?:tgz|tar.gz|tar)$/i
17+
const isFileType = /[.](?:tgz|tar.gz|tar)$/i
2018
const isPortNumber = /:[0-9]+(\/|$)/i
19+
const isWindowsFile = /^(?:[.]|~[/]|[/\\]|[a-zA-Z]:)/
20+
const isPosixFile = /^(?:[.]|~[/]|[/]|[a-zA-Z]:)/
21+
const defaultRegistry = 'https://registry.npmjs.org'
2122

2223
function npa (arg, where) {
2324
let name
@@ -31,13 +32,14 @@ function npa (arg, where) {
3132
return npa(arg.raw, where || arg.where)
3233
}
3334
}
34-
const nameEndsAt = arg[0] === '@' ? arg.slice(1).indexOf('@') + 1 : arg.indexOf('@')
35+
const nameEndsAt = arg.indexOf('@', 1) // Skip possible leading @
3536
const namePart = nameEndsAt > 0 ? arg.slice(0, nameEndsAt) : arg
3637
if (isURL.test(arg)) {
3738
spec = arg
3839
} else if (isGit.test(arg)) {
3940
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))) {
4143
spec = arg
4244
} else if (nameEndsAt > 0) {
4345
name = namePart
@@ -54,7 +56,27 @@ function npa (arg, where) {
5456
return resolve(name, spec, where, arg)
5557
}
5658

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+
}
5880

5981
function resolve (name, spec, where, arg) {
6082
const res = new Result({
@@ -65,12 +87,16 @@ function resolve (name, spec, where, arg) {
6587
})
6688

6789
if (name) {
68-
res.setName(name)
90+
res.name = name
6991
}
7092

71-
if (spec && (isFilespec.test(spec) || /^file:/i.test(spec))) {
93+
if (!where) {
94+
where = process.cwd()
95+
}
96+
97+
if (isFileSpec(spec)) {
7298
return fromFile(res, where)
73-
} else if (spec && /^npm:/i.test(spec)) {
99+
} else if (isAliasSpec(spec)) {
74100
return fromAlias(res, where)
75101
}
76102

@@ -82,15 +108,13 @@ function resolve (name, spec, where, arg) {
82108
return fromHostedGit(res, hosted)
83109
} else if (spec && isURL.test(spec)) {
84110
return fromURL(res)
85-
} else if (spec && (hasSlashes.test(spec) || isFilename.test(spec))) {
111+
} else if (spec && (hasSlashes.test(spec) || isFileType.test(spec))) {
86112
return fromFile(res, where)
87113
} else {
88114
return fromRegistry(res)
89115
}
90116
}
91117

92-
const defaultRegistry = 'https://registry.npmjs.org'
93-
94118
function toPurl (arg, reg = defaultRegistry) {
95119
const res = npa(arg)
96120

@@ -128,60 +152,62 @@ function invalidPurlType (type, raw) {
128152
return err
129153
}
130154

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
139178
}
140179

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+
}
155186

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
160192
}
161193

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
177204
}
178-
return full.length ? full.join('@') : this.raw
179-
}
180205

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+
}
185211
}
186212

187213
// sets res.gitCommittish, res.gitRange, and res.gitSubdir
@@ -228,25 +254,67 @@ function setGitAttrs (res, committish) {
228254
}
229255
}
230256

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
234287
}
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'
236293
res.where = where
237294

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+
}
241311

242-
let specUrl
243312
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
247314
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)
250318
} catch (originalError) {
251319
const er = new Error('Invalid file: URL, must comply with RFC 8089')
252320
throw Object.assign(er, {
@@ -257,24 +325,6 @@ function fromFile (res, where) {
257325
})
258326
}
259327

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-
278328
// turn /C:/blah into just C:/blah on windows
279329
let specPath = decodeURIComponent(specUrl.pathname)
280330
let resolvedPath = decodeURIComponent(resolvedUrl.pathname)
@@ -288,13 +338,21 @@ function fromFile (res, where) {
288338
if (/^\/~(\/|$)/.test(specPath)) {
289339
res.saveSpec = `file:${specPath.substr(1)}`
290340
resolvedPath = path.resolve(homedir(), specPath.substr(3))
291-
} else if (!path.isAbsolute(rawNoPrefix)) {
341+
} else if (!path.isAbsolute(rawSpec.slice(5))) {
292342
res.saveSpec = `file:${path.relative(where, resolvedPath)}`
293343
} else {
294344
res.saveSpec = `file:${path.resolve(resolvedPath)}`
295345
}
296346

297347
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+
}
298356
return res
299357
}
300358

@@ -416,3 +474,8 @@ function fromRegistry (res) {
416474
}
417475
return res
418476
}
477+
478+
module.exports = npa
479+
module.exports.resolve = resolve
480+
module.exports.toPurl = toPurl
481+
module.exports.Result = Result

‎node_modules/npm-package-arg/package.json

+3-3
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "npm-package-arg",
3-
"version": "12.0.1",
3+
"version": "12.0.2",
44
"description": "Parse the things that can be arguments to `npm install`",
55
"main": "./lib/npa.js",
66
"directories": {
@@ -18,7 +18,7 @@
1818
},
1919
"devDependencies": {
2020
"@npmcli/eslint-config": "^5.0.0",
21-
"@npmcli/template-oss": "4.23.4",
21+
"@npmcli/template-oss": "4.23.5",
2222
"tap": "^16.0.1"
2323
},
2424
"scripts": {
@@ -55,7 +55,7 @@
5555
},
5656
"templateOSS": {
5757
"//@npmcli/template-oss": "This file is partially managed by @npmcli/template-oss. Edits may be overwritten.",
58-
"version": "4.23.4",
58+
"version": "4.23.5",
5959
"publish": true
6060
}
6161
}

0 commit comments

Comments
 (0)