Skip to content

Commit a44bd35

Browse files
committedOct 26, 2022
feat: add separate static method for just parsing urls
1 parent cb7cbc2 commit a44bd35

File tree

5 files changed

+108
-86
lines changed

5 files changed

+108
-86
lines changed
 

‎lib/from-url.js

+3-77
Original file line numberDiff line numberDiff line change
@@ -1,44 +1,6 @@
11
'use strict'
22

3-
const url = require('url')
4-
5-
const safeUrl = (u) => {
6-
try {
7-
return new url.URL(u)
8-
} catch {
9-
// this fn should never throw
10-
}
11-
}
12-
13-
const lastIndexOfBefore = (str, char, beforeChar) => {
14-
const startPosition = str.indexOf(beforeChar)
15-
return str.lastIndexOf(char, startPosition > -1 ? startPosition : Infinity)
16-
}
17-
18-
// accepts input like git:github.com:user/repo and inserts the // after the first :
19-
const correctProtocol = (arg, protocols) => {
20-
const firstColon = arg.indexOf(':')
21-
const proto = arg.slice(0, firstColon + 1)
22-
if (Object.prototype.hasOwnProperty.call(protocols, proto)) {
23-
return arg
24-
}
25-
26-
const firstAt = arg.indexOf('@')
27-
if (firstAt > -1) {
28-
if (firstAt > firstColon) {
29-
return `git+ssh://${arg}`
30-
} else {
31-
return arg
32-
}
33-
}
34-
35-
const doubleSlash = arg.indexOf('//')
36-
if (doubleSlash === firstColon + 1) {
37-
return arg
38-
}
39-
40-
return `${arg.slice(0, firstColon + 1)}//${arg.slice(firstColon + 1)}`
41-
}
3+
const parseUrl = require('./parse-url')
424

435
// look for github shorthand inputs, such as npm/cli
446
const isGitHubShorthand = (arg) => {
@@ -71,49 +33,13 @@ const isGitHubShorthand = (arg) => {
7133
secondSlashOnlyAfterHash
7234
}
7335

74-
// attempt to correct an scp style url so that it will parse with `new URL()`
75-
const correctUrl = (giturl) => {
76-
// ignore @ that come after the first hash since the denotes the start
77-
// of a committish which can contain @ characters
78-
const firstAt = lastIndexOfBefore(giturl, '@', '#')
79-
// ignore colons that come after the hash since that could include colons such as:
80-
// git@github.com:user/package-2#semver:^1.0.0
81-
const lastColonBeforeHash = lastIndexOfBefore(giturl, ':', '#')
82-
83-
if (lastColonBeforeHash > firstAt) {
84-
// the last : comes after the first @ (or there is no @)
85-
// like it would in:
86-
// proto://hostname.com:user/repo
87-
// username@hostname.com:user/repo
88-
// :password@hostname.com:user/repo
89-
// username:password@hostname.com:user/repo
90-
// proto://username@hostname.com:user/repo
91-
// proto://:password@hostname.com:user/repo
92-
// proto://username:password@hostname.com:user/repo
93-
// then we replace the last : with a / to create a valid path
94-
giturl = giturl.slice(0, lastColonBeforeHash) + '/' + giturl.slice(lastColonBeforeHash + 1)
95-
}
96-
97-
if (lastIndexOfBefore(giturl, ':', '#') === -1 && giturl.indexOf('//') === -1) {
98-
// we have no : at all
99-
// as it would be in:
100-
// username@hostname.com/user/repo
101-
// then we prepend a protocol
102-
giturl = `git+ssh://${giturl}`
103-
}
104-
105-
return giturl
106-
}
107-
10836
module.exports = (giturl, opts, { gitHosts, protocols }) => {
10937
if (!giturl) {
11038
return
11139
}
11240

113-
const correctedUrl = isGitHubShorthand(giturl)
114-
? `github:${giturl}`
115-
: correctProtocol(giturl, protocols)
116-
const parsed = safeUrl(correctedUrl) || safeUrl(correctUrl(correctedUrl))
41+
const correctedUrl = isGitHubShorthand(giturl) ? `github:${giturl}` : giturl
42+
const parsed = parseUrl(correctedUrl, protocols)
11743
if (!parsed) {
11844
return
11945
}

‎lib/index.js

+7-9
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
const LRU = require('lru-cache')
44
const hosts = require('./hosts.js')
55
const fromUrl = require('./from-url.js')
6+
const parseUrl = require('./parse-url.js')
7+
const getProtocols = require('./protocols.js')
68

79
const cache = new LRU({ max: 1000 })
810

@@ -20,15 +22,7 @@ class GitHost {
2022
}
2123

2224
static #gitHosts = { byShortcut: {}, byDomain: {} }
23-
static #protocols = {
24-
'git+ssh:': { name: 'sshurl' },
25-
'ssh:': { name: 'sshurl' },
26-
'git+https:': { name: 'https', auth: true },
27-
'git:': { auth: true },
28-
'http:': { auth: true },
29-
'https:': { auth: true },
30-
'git+http:': { auth: true },
31-
}
25+
static #protocols = getProtocols()
3226

3327
static addHost (name, host) {
3428
GitHost.#gitHosts[name] = host
@@ -55,6 +49,10 @@ class GitHost {
5549
return cache.get(key)
5650
}
5751

52+
static parseUrl (url) {
53+
return parseUrl(url)
54+
}
55+
5856
#fill (template, opts) {
5957
if (typeof template !== 'function') {
6058
return null

‎lib/parse-url.js

+79
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
const url = require('url')
2+
const getProtocols = require('./protocols.js')
3+
4+
const lastIndexOfBefore = (str, char, beforeChar) => {
5+
const startPosition = str.indexOf(beforeChar)
6+
return str.lastIndexOf(char, startPosition > -1 ? startPosition : Infinity)
7+
}
8+
9+
const safeUrl = (u) => {
10+
try {
11+
return new url.URL(u)
12+
} catch {
13+
// this fn should never throw
14+
}
15+
}
16+
17+
// accepts input like git:github.com:user/repo and inserts the // after the first :
18+
const correctProtocol = (arg, protocols) => {
19+
const firstColon = arg.indexOf(':')
20+
const proto = arg.slice(0, firstColon + 1)
21+
if (Object.prototype.hasOwnProperty.call(protocols, proto)) {
22+
return arg
23+
}
24+
25+
const firstAt = arg.indexOf('@')
26+
if (firstAt > -1) {
27+
if (firstAt > firstColon) {
28+
return `git+ssh://${arg}`
29+
} else {
30+
return arg
31+
}
32+
}
33+
34+
const doubleSlash = arg.indexOf('//')
35+
if (doubleSlash === firstColon + 1) {
36+
return arg
37+
}
38+
39+
return `${arg.slice(0, firstColon + 1)}//${arg.slice(firstColon + 1)}`
40+
}
41+
42+
// attempt to correct an scp style url so that it will parse with `new URL()`
43+
const correctUrl = (giturl) => {
44+
// ignore @ that come after the first hash since the denotes the start
45+
// of a committish which can contain @ characters
46+
const firstAt = lastIndexOfBefore(giturl, '@', '#')
47+
// ignore colons that come after the hash since that could include colons such as:
48+
// git@github.com:user/package-2#semver:^1.0.0
49+
const lastColonBeforeHash = lastIndexOfBefore(giturl, ':', '#')
50+
51+
if (lastColonBeforeHash > firstAt) {
52+
// the last : comes after the first @ (or there is no @)
53+
// like it would in:
54+
// proto://hostname.com:user/repo
55+
// username@hostname.com:user/repo
56+
// :password@hostname.com:user/repo
57+
// username:password@hostname.com:user/repo
58+
// proto://username@hostname.com:user/repo
59+
// proto://:password@hostname.com:user/repo
60+
// proto://username:password@hostname.com:user/repo
61+
// then we replace the last : with a / to create a valid path
62+
giturl = giturl.slice(0, lastColonBeforeHash) + '/' + giturl.slice(lastColonBeforeHash + 1)
63+
}
64+
65+
if (lastIndexOfBefore(giturl, ':', '#') === -1 && giturl.indexOf('//') === -1) {
66+
// we have no : at all
67+
// as it would be in:
68+
// username@hostname.com/user/repo
69+
// then we prepend a protocol
70+
giturl = `git+ssh://${giturl}`
71+
}
72+
73+
return giturl
74+
}
75+
76+
module.exports = (giturl, protocols = getProtocols()) => {
77+
const withProtocol = correctProtocol(giturl, protocols)
78+
return safeUrl(withProtocol) || safeUrl(correctUrl(withProtocol))
79+
}

‎lib/protocols.js

+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
module.exports = () => ({
2+
'git+ssh:': { name: 'sshurl' },
3+
'ssh:': { name: 'sshurl' },
4+
'git+https:': { name: 'https', auth: true },
5+
'git:': { auth: true },
6+
'http:': { auth: true },
7+
'https:': { auth: true },
8+
'git+http:': { auth: true },
9+
})

‎test/parse-url.js

+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
const t = require('tap')
2+
const HostedGit = require('..')
3+
const parseUrl = require('../lib/parse-url.js')
4+
5+
t.test('can parse git+ssh url by default', async t => {
6+
// https://github.com/npm/cli/issues/5278
7+
const u = 'git+ssh://git@abc:frontend/utils.git#6d45447e0c5eb6cd2e3edf05a8c5a9bb81950c79'
8+
t.ok(parseUrl(u))
9+
t.ok(HostedGit.parseUrl(u))
10+
})

0 commit comments

Comments
 (0)
Please sign in to comment.