mirror of
https://github.com/pnpm/pnpm.git
synced 2026-05-19 14:20:36 -04:00
fix(git-resolver): avoid encoded slash in GitLab tarball URL (#11551)
* fix(git-resolver): avoid encoded slash in GitLab tarball URL hosted-git-info's default GitLab tarball URL routes through `/api/v4/projects/<user>%2F<project>/...`. The `%2F` survives into the virtual store directory name (depPathToFilename only escapes raw `/`, not `%`), and Node refuses to import any module whose path contains an encoded slash. The same URL is also intermittently rejected by GitLab with a 406. Override the GitLab tarballtemplate to the `/-/archive/` URL, which works for both public and private repos and contains no encoded slashes. Closes #11533 * test: avoid cspell-flagged words * test: keep existing gitlab assertions, only add new ones Restore the skipped tests' original API-URL assertions; they document the old expected shape and weren't running anyway. Add the new `/-/archive/` URL to the pick-fetcher fixture as an additional case so both shapes are exercised.
This commit is contained in:
6
.changeset/gitlab-tarball-archive-url.md
Normal file
6
.changeset/gitlab-tarball-archive-url.md
Normal file
@@ -0,0 +1,6 @@
|
||||
---
|
||||
"@pnpm/resolving.git-resolver": patch
|
||||
"pnpm": patch
|
||||
---
|
||||
|
||||
Fixed installation of GitLab-hosted dependencies. pnpm now downloads the tarball from `https://gitlab.com/<user>/<project>/-/archive/<sha>/<project>-<sha>.tar.gz` instead of the GitLab API endpoint that contained an encoded slash (`%2F`) between user and project. The encoded slash both triggered `406 Not Acceptable` responses from GitLab and produced virtual store directory names that Node refused to import (`ERR_INVALID_MODULE_SPECIFIER`) [#11533](https://github.com/pnpm/pnpm/issues/11533).
|
||||
@@ -330,6 +330,7 @@
|
||||
"szia",
|
||||
"tabtab",
|
||||
"taffydb",
|
||||
"tarballtemplate",
|
||||
"teambit",
|
||||
"tempy",
|
||||
"testcase",
|
||||
|
||||
@@ -33,6 +33,7 @@ test.each([
|
||||
'https://codeload.github.com/zkochan/is-negative/tar.gz/6dcce91c268805d456b8a575b67d7febc7ae2933',
|
||||
'https://bitbucket.org/pnpmjs/git-resolver/get/87cf6a67064d2ce56e8cd20624769a5512b83ff9.tar.gz',
|
||||
'https://gitlab.com/api/v4/projects/pnpm%2Fgit-resolver/repository/archive.tar.gz',
|
||||
'https://gitlab.com/pnpm/git-resolver/-/archive/988c61e11dc8d9ca0b5580cb15291951812549dc/git-resolver-988c61e11dc8d9ca0b5580cb15291951812549dc.tar.gz',
|
||||
])('should pick gitHostedTarball fetcher', async (tarball) => {
|
||||
const gitHostedTarball = jest.fn() as FetchFunction
|
||||
const fetcher = await pickFetcher(createMockFetchers({ gitHostedTarball }), { tarball })
|
||||
|
||||
@@ -122,6 +122,7 @@ async function fromHostedGit (hosted: any, dispatcherOptions: DispatcherOptions)
|
||||
fetchSpec: fetchSpec!,
|
||||
hosted: {
|
||||
...hosted,
|
||||
tarballtemplate: hosted.type === 'gitlab' ? gitlabTarballTemplate : hosted.tarballtemplate,
|
||||
_fill: hosted._fill,
|
||||
tarball: hosted.tarball,
|
||||
},
|
||||
@@ -130,6 +131,14 @@ async function fromHostedGit (hosted: any, dispatcherOptions: DispatcherOptions)
|
||||
}
|
||||
}
|
||||
|
||||
// hosted-git-info's default GitLab tarball URL contains an encoded slash
|
||||
// (`%2F`) which survives into the virtual store directory name and makes
|
||||
// Node refuse to import the package (ERR_INVALID_MODULE_SPECIFIER).
|
||||
function gitlabTarballTemplate ({ domain, user, project, committish }: { domain: string, user: string, project: string, committish: string | null }): string {
|
||||
const ref = committish ? encodeURIComponent(committish) : 'HEAD'
|
||||
return `https://${domain}/${user}/${project}/-/archive/${ref}/${project}-${ref}.tar.gz`
|
||||
}
|
||||
|
||||
async function isRepoPublic (httpsUrl: string, dispatcherOptions: DispatcherOptions): Promise<boolean> {
|
||||
try {
|
||||
const response = await fetchWithDispatcher(httpsUrl.replace(/\.git$/, ''), { method: 'HEAD', redirect: 'manual', retry: { retries: 0 }, dispatcherOptions })
|
||||
|
||||
@@ -370,6 +370,27 @@ test('resolveFromGit() gitlab with colon in the URL', async () => {
|
||||
})
|
||||
})
|
||||
|
||||
// Regression test for #11533: the tarball URL must not contain `%2F`,
|
||||
// otherwise GitLab returns 406 and Node refuses to import the package
|
||||
// (the encoded slash ends up in the virtual store directory name).
|
||||
test('resolveFromGit() gitlab tarball uses /-/archive/ URL without encoded slash', async () => {
|
||||
const headCommit = '988c61e11dc8d9ca0b5580cb15291951812549dc'
|
||||
jest.mocked(fetchWithDispatcher).mockImplementation(async (_url, _opts) => {
|
||||
return { ok: true } as any // eslint-disable-line @typescript-eslint/no-explicit-any
|
||||
})
|
||||
jest.mocked(git).mockImplementation(async () => ({ stdout: `${headCommit}\tHEAD` }))
|
||||
const resolveResult = await resolveFromGit({ bareSpecifier: 'https://gitlab.com/pnpmjs/git-resolver' })
|
||||
expect(resolveResult).toStrictEqual({
|
||||
id: `https://gitlab.com/pnpmjs/git-resolver/-/archive/${headCommit}/git-resolver-${headCommit}.tar.gz`,
|
||||
normalizedBareSpecifier: 'gitlab:pnpmjs/git-resolver',
|
||||
resolution: {
|
||||
tarball: `https://gitlab.com/pnpmjs/git-resolver/-/archive/${headCommit}/git-resolver-${headCommit}.tar.gz`,
|
||||
gitHosted: true,
|
||||
},
|
||||
resolvedVia: 'git-repository',
|
||||
})
|
||||
})
|
||||
|
||||
// This test stopped working. Probably an environmental issue.
|
||||
test.skip('resolveFromGit() gitlab with commit', async () => {
|
||||
const resolveResult = await resolveFromGit({ bareSpecifier: 'gitlab:pnpm/git-resolver#988c61e11dc8d9ca0b5580cb15291951812549dc' })
|
||||
|
||||
Reference in New Issue
Block a user