mirror of
https://github.com/pnpm/pnpm.git
synced 2026-03-30 04:52:04 -04:00
feat(git-resolver): fix private git repo (#6832)
fixes #6827 closes #6829
This commit is contained in:
6
.changeset/silly-dolls-grin.md
Normal file
6
.changeset/silly-dolls-grin.md
Normal file
@@ -0,0 +1,6 @@
|
||||
---
|
||||
"pnpm": patch
|
||||
"@pnpm/git-resolver": patch
|
||||
---
|
||||
|
||||
Pass the right scheme to `git ls-remote` in order to prevent a fallback to `git+ssh` that would result in a 'host key verification failed' issue [#6806](https://github.com/pnpm/pnpm/issues/6806)
|
||||
6
resolving/git-resolver/__mocks__/@pnpm/fetch.js
Normal file
6
resolving/git-resolver/__mocks__/@pnpm/fetch.js
Normal file
@@ -0,0 +1,6 @@
|
||||
module.exports = jest.createMockFromModule('@pnpm/fetch')
|
||||
|
||||
// default implementation
|
||||
module.exports.fetch.mockImplementation(async (_url, _opts) => {
|
||||
return { ok: true }
|
||||
})
|
||||
@@ -64,9 +64,14 @@ function urlToFetchSpec (urlparse: URL) {
|
||||
async function fromHostedGit (hosted: any): Promise<HostedPackageSpec> { // eslint-disable-line
|
||||
let fetchSpec: string | null = null
|
||||
// try git/https url before fallback to ssh url
|
||||
const gitUrl = hosted.https({ noCommittish: true }) ?? hosted.ssh({ noCommittish: true })
|
||||
if (gitUrl && await accessRepository(gitUrl)) {
|
||||
fetchSpec = gitUrl
|
||||
const gitHttpsUrl = hosted.https({ noCommittish: true, noGitPlus: true })
|
||||
if (gitHttpsUrl && await isRepoPublic(gitHttpsUrl) && await accessRepository(gitHttpsUrl)) {
|
||||
fetchSpec = gitHttpsUrl
|
||||
} else {
|
||||
const gitSshUrl = hosted.ssh({ noCommittish: true })
|
||||
if (gitSshUrl && await accessRepository(gitSshUrl)) {
|
||||
fetchSpec = gitSshUrl
|
||||
}
|
||||
}
|
||||
|
||||
if (!fetchSpec) {
|
||||
@@ -91,7 +96,7 @@ async function fromHostedGit (hosted: any): Promise<HostedPackageSpec> { // esli
|
||||
// npm instead tries git ls-remote directly which prompts user for login credentials.
|
||||
|
||||
// HTTP HEAD on https://domain/user/repo, strip out ".git"
|
||||
const response = await fetch(httpsUrl.slice(0, -4), { method: 'HEAD', follow: 0, retry: { retries: 0 } })
|
||||
const response = await fetch(httpsUrl.replace(/\.git$/, ''), { method: 'HEAD', follow: 0, retry: { retries: 0 } })
|
||||
if (response.ok) {
|
||||
fetchSpec = httpsUrl
|
||||
}
|
||||
@@ -119,6 +124,15 @@ async function fromHostedGit (hosted: any): Promise<HostedPackageSpec> { // esli
|
||||
}
|
||||
}
|
||||
|
||||
async function isRepoPublic (httpsUrl: string) {
|
||||
try {
|
||||
const response = await fetch(httpsUrl.replace(/\.git$/, ''), { method: 'HEAD', follow: 0, retry: { retries: 0 } })
|
||||
return response.ok
|
||||
} catch (_err) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
async function accessRepository (repository: string) {
|
||||
try {
|
||||
await git(['ls-remote', '--exit-code', repository, 'HEAD'], { retries: 0 })
|
||||
|
||||
@@ -3,9 +3,18 @@ import path from 'path'
|
||||
import { createGitResolver } from '@pnpm/git-resolver'
|
||||
import git from 'graceful-git'
|
||||
import isWindows from 'is-windows'
|
||||
import { fetch } from '@pnpm/fetch'
|
||||
|
||||
const resolveFromGit = createGitResolver({})
|
||||
|
||||
function mockFetchAsPrivate (): void {
|
||||
type Fetch = typeof fetch
|
||||
type MockedFetch = jest.MockedFunction<Fetch>
|
||||
(fetch as MockedFetch).mockImplementation(async (_url, _opts) => {
|
||||
return { ok: false } as any // eslint-disable-line @typescript-eslint/no-explicit-any
|
||||
})
|
||||
}
|
||||
|
||||
test('resolveFromGit() with commit', async () => {
|
||||
const resolveResult = await resolveFromGit({ pref: 'zkochan/is-negative#163360a8d3ae6bee9524541043197ff356f8ed99' })
|
||||
expect(resolveResult).toStrictEqual({
|
||||
@@ -386,6 +395,7 @@ test('resolveFromGit() normalizes full url (alternative form 2)', async () => {
|
||||
// This test relies on implementation detail.
|
||||
// current implementation does not try git ls-remote --refs on pref with full commit hash, this fake repo url will pass.
|
||||
test('resolveFromGit() private repo with commit hash', async () => {
|
||||
mockFetchAsPrivate()
|
||||
const resolveResult = await resolveFromGit({ pref: 'fake/private-repo#2fa0531ab04e300a24ef4fd7fb3a280eccb7ccc5' })
|
||||
expect(resolveResult).toStrictEqual({
|
||||
id: 'github.com/fake/private-repo/2fa0531ab04e300a24ef4fd7fb3a280eccb7ccc5',
|
||||
@@ -399,6 +409,32 @@ test('resolveFromGit() private repo with commit hash', async () => {
|
||||
})
|
||||
})
|
||||
|
||||
test('resolve a private repository using the HTTPS protocol without auth token', async () => {
|
||||
git.mockImplementation(async (args: string[]) => {
|
||||
expect(args).toContain('git+ssh://git@github.com/foo/bar.git')
|
||||
if (args.includes('--refs')) {
|
||||
return {
|
||||
stdout: `\n${'a'.repeat(40)}\trefs/heads/master\n`,
|
||||
}
|
||||
}
|
||||
return {
|
||||
stdout: '0'.repeat(40) + '\tHEAD',
|
||||
}
|
||||
})
|
||||
mockFetchAsPrivate()
|
||||
const resolveResult = await resolveFromGit({ pref: 'git+https://github.com/foo/bar.git' })
|
||||
expect(resolveResult).toStrictEqual({
|
||||
id: 'github.com/foo/bar/0000000000000000000000000000000000000000',
|
||||
normalizedPref: 'github:foo/bar',
|
||||
resolution: {
|
||||
commit: '0000000000000000000000000000000000000000',
|
||||
repo: 'git+ssh://git@github.com/foo/bar.git',
|
||||
type: 'git',
|
||||
},
|
||||
resolvedVia: 'git-repository',
|
||||
})
|
||||
})
|
||||
|
||||
test('resolve a private repository using the HTTPS protocol and an auth token', async () => {
|
||||
git.mockImplementation(async (args: string[]) => {
|
||||
if (!args.includes('https://0000000000000000000000000000000000000000:x-oauth-basic@github.com/foo/bar.git')) throw new Error('')
|
||||
@@ -411,6 +447,7 @@ aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\trefs/heads/master\
|
||||
}
|
||||
return { stdout: '0000000000000000000000000000000000000000\tHEAD' }
|
||||
})
|
||||
mockFetchAsPrivate()
|
||||
const resolveResult = await resolveFromGit({ pref: 'git+https://0000000000000000000000000000000000000000:x-oauth-basic@github.com/foo/bar.git' })
|
||||
expect(resolveResult).toStrictEqual({
|
||||
id: '0000000000000000000000000000000000000000+x-oauth-basic@github.com/foo/bar/0000000000000000000000000000000000000000',
|
||||
|
||||
Reference in New Issue
Block a user