Skip to content

Commit

Permalink
feat: add HostedGitInfo.fromManifest
Browse files Browse the repository at this point in the history
This encapsulates the logic used in `npm repo`
  • Loading branch information
ljharb committed Dec 16, 2024
1 parent 3baf852 commit c4e1381
Show file tree
Hide file tree
Showing 3 changed files with 205 additions and 0 deletions.
51 changes: 51 additions & 0 deletions lib/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,27 @@ const parseUrl = require('./parse-url.js')

const cache = new LRUCache({ max: 1000 })

function unknownHostedUrl (url) {
try {
const {
protocol,
hostname,
pathname,
} = new URL(url)

/* istanbul ignore next - URL ctor should prevent this */
if (!protocol || !hostname) {
return null
}

const proto = /(?:git\+)http:$/.test(protocol) ? 'http:' : 'https:'
const path = pathname.replace(/\.git$/, '')
return `${proto}//${hostname}${path}`
} catch {
return null
}
}

class GitHost {
constructor (type, user, auth, project, committish, defaultRepresentation, opts = {}) {
Object.assign(this, GitHost.#gitHosts[type], {
Expand Down Expand Up @@ -56,6 +77,36 @@ class GitHost {
return cache.get(key)
}

static fromManifest (manifest, opts = {}) {
if (!manifest || typeof manifest !== 'object') {
return
}

const r = manifest.repository
// TODO: look into also checking the `bugs`/`homepage` URLs

const rurl = r && (
typeof r === 'string'
? r
: typeof r === 'object' && typeof r.url === 'string'
? r.url
: null
)

if (!rurl) {
throw new Error('no repository')
}

const info = (rurl && this.fromUrl(rurl.replace(/^git\+/, ''), opts)) || null
const url = info ? info.browse(r.directory) : unknownHostedUrl(rurl)

if (!url) {
throw new Error('no repository: could not get URL')
}

return url
}

static parseUrl (url) {
return parseUrl(url)
}
Expand Down
77 changes: 77 additions & 0 deletions test/github.js
Original file line number Diff line number Diff line change
Expand Up @@ -270,3 +270,80 @@ t.test('string methods populate correctly', t => {

t.end()
})

t.test('from manifest', t => {
t.equal(HostedGit.fromManifest(), undefined, 'no manifest returns undefined')
t.equal(HostedGit.fromManifest(), undefined, 'no manifest returns undefined')
t.equal(HostedGit.fromManifest(false), undefined, 'false manifest returns undefined')
t.equal(HostedGit.fromManifest(() => {}), undefined, 'function manifest returns undefined')

const unknownHostRepo = {
name: 'foo',
repository: {
url: 'https://nope.com',
},
}
t.equal(HostedGit.fromManifest(unknownHostRepo), 'https://nope.com/')

const insecureUnknownHostRepo = {
name: 'foo',
repository: {
url: 'http://nope.com',
},
}
t.equal(HostedGit.fromManifest(insecureUnknownHostRepo), 'https://nope.com/')

const insecureGitUnknownHostRepo = {
name: 'foo',
repository: {
url: 'git+http://nope.com',
},
}
t.equal(HostedGit.fromManifest(insecureGitUnknownHostRepo), 'http://nope.com')

const badRepo = {
name: 'foo',
repository: {
url: '#',
},
}
t.throws(() => HostedGit.fromManifest(badRepo), Error, 'bad repo throws')

const manifest = {
name: 'foo',
repository: {
type: 'git',
url: 'git+ssh://github.com/foo/bar.git',
},
}

const parsed = HostedGit.fromManifest(manifest)
t.equal(parsed, 'https://github.com/foo/bar')

const monorepo = {
name: 'clowncar',
repository: {
type: 'git',
url: 'git+ssh://github.com/foo/bar.git',
directory: 'packages/foo',
},
}

const honk = HostedGit.fromManifest(monorepo)
t.equal(honk, 'https://github.com/foo/bar/tree/HEAD/packages/foo')

const stringRepo = {
name: 'foo',
repository: 'git+ssh://github.com/foo/bar.git',
}
const stringRepoParsed = HostedGit.fromManifest(stringRepo)
t.equal(stringRepoParsed, 'https://github.com/foo/bar')

const nonStringRepo = {
name: 'foo',
repository: 42,
}
t.throws(() => HostedGit.fromManifest(nonStringRepo))

t.end()
})
77 changes: 77 additions & 0 deletions test/gitlab.js
Original file line number Diff line number Diff line change
Expand Up @@ -321,3 +321,80 @@ t.test('string methods populate correctly', t => {

t.end()
})

t.test('from manifest', t => {
t.equal(HostedGit.fromManifest(), undefined, 'no manifest returns undefined')
t.equal(HostedGit.fromManifest(), undefined, 'no manifest returns undefined')
t.equal(HostedGit.fromManifest(false), undefined, 'false manifest returns undefined')
t.equal(HostedGit.fromManifest(() => {}), undefined, 'function manifest returns undefined')

const unknownHostRepo = {
name: 'foo',
repository: {
url: 'https://nope.com',
},
}
t.equal(HostedGit.fromManifest(unknownHostRepo), 'https://nope.com/')

const insecureUnknownHostRepo = {
name: 'foo',
repository: {
url: 'http://nope.com',
},
}
t.equal(HostedGit.fromManifest(insecureUnknownHostRepo), 'https://nope.com/')

const insecureGitUnknownHostRepo = {
name: 'foo',
repository: {
url: 'git+http://nope.com',
},
}
t.equal(HostedGit.fromManifest(insecureGitUnknownHostRepo), 'http://nope.com')

const badRepo = {
name: 'foo',
repository: {
url: '#',
},
}
t.throws(() => HostedGit.fromManifest(badRepo), Error, 'bad repo throws')

const manifest = {
name: 'foo',
repository: {
type: 'git',
url: 'git+ssh://gitlab.com/foo/bar.git',
},
}

const parsed = HostedGit.fromManifest(manifest)
t.equal(parsed, 'https://gitlab.com/foo/bar')

const monorepo = {
name: 'clowncar',
repository: {
type: 'git',
url: 'git+ssh://gitlab.com/foo/bar.git',
directory: 'packages/foo',
},
}

const honk = HostedGit.fromManifest(monorepo)
t.equal(honk, 'https://gitlab.com/foo/bar/tree/HEAD/packages/foo')

const stringRepo = {
name: 'foo',
repository: 'git+ssh://gitlab.com/foo/bar.git',
}
const stringRepoParsed = HostedGit.fromManifest(stringRepo)
t.equal(stringRepoParsed, 'https://gitlab.com/foo/bar')

const nonStringRepo = {
name: 'foo',
repository: 42,
}
t.throws(() => HostedGit.fromManifest(nonStringRepo))

t.end()
})

0 comments on commit c4e1381

Please sign in to comment.