fix: normalize tarball URLs by removing default HTTP/HTTPS ports (#10273)

* fix: normalize tarball URLs by removing default HTTP/HTTPS ports

closes #6725

* feat: refactor, add test and changeset

---------

Co-authored-by: Zoltan Kochan <z@kochan.io>
This commit is contained in:
Minijus L
2025-12-11 09:04:39 +02:00
committed by Zoltan Kochan
parent 73cc63504d
commit cfec937abd
5 changed files with 62 additions and 2 deletions

View File

@@ -0,0 +1,6 @@
---
"@pnpm/npm-resolver": patch
"pnpm": patch
---
Normalize the tarball URLs before saving them to the lockfile. URLs should not contain default ports, like :80 for http and :443 for https [#10273](https://github.com/pnpm/pnpm/pull/10273).

View File

@@ -50,6 +50,7 @@ import { workspacePrefToNpm } from './workspacePrefToNpm.js'
import { whichVersionIsPinned } from './whichVersionIsPinned.js'
import { pickVersionByVersionRange } from './pickPackageFromMeta.js'
import { failIfTrustDowngraded } from './trustChecks.js'
import { normalizeRegistryUrl } from './normalizeRegistryUrl.js'
export interface NoMatchingVersionErrorOptions {
wantedDependency: WantedDependency
@@ -348,7 +349,7 @@ async function resolveNpm (
const id = `${pickedPackage.name}@${pickedPackage.version}` as PkgResolutionId
const resolution = {
integrity: getIntegrity(pickedPackage.dist),
tarball: pickedPackage.dist.tarball,
tarball: normalizeRegistryUrl(pickedPackage.dist.tarball),
}
let normalizedBareSpecifier: string | undefined
if (opts.calcSpecifier) {
@@ -400,7 +401,7 @@ async function resolveJsr (
const id = `${pickedPackage.name}@${pickedPackage.version}` as PkgResolutionId
const resolution = {
integrity: getIntegrity(pickedPackage.dist),
tarball: pickedPackage.dist.tarball,
tarball: normalizeRegistryUrl(pickedPackage.dist.tarball),
}
return {
id,

View File

@@ -0,0 +1,10 @@
/**
* Remove default ports (80 for HTTP, 443 for HTTPS) to ensure consistency
*/
export function normalizeRegistryUrl (urlString: string): string {
try {
return new URL(urlString).toString()
} catch {
return urlString
}
}

View File

@@ -79,6 +79,35 @@ test('resolveFromNpm()', async () => {
expect(meta['dist-tags']).toBeTruthy()
})
test('resolveFromNpm() strips port 80 from http tarball URLs', async () => {
nock(registries.default)
.get('/is-positive')
.reply(200, {
...isPositiveMeta,
versions: {
'1.0.0': {
...isPositiveMeta.versions['1.0.0'],
dist: {
...isPositiveMeta.versions['1.0.0'].dist,
tarball: 'http://registry.npmjs.org:80/is-positive/-/is-positive-1.0.0.tgz',
},
},
},
})
const cacheDir = temporaryDirectory()
const { resolveFromNpm } = createResolveFromNpm({
cacheDir,
registries,
})
const resolveResult = await resolveFromNpm({ alias: 'is-positive', bareSpecifier: '1.0.0' }, { calcSpecifier: true })
expect(resolveResult!.resolution).toStrictEqual({
integrity: 'sha512-9cI+DmhNhA8ioT/3EJFnt0s1yehnAECyIOXdT+2uQGzcEEBaj8oNmVWj33+ZjPndMIFRQh8JeJlEu1uv5/J7pQ==',
tarball: 'http://registry.npmjs.org/is-positive/-/is-positive-1.0.0.tgz',
})
})
test('resolveFromNpm() does not save mutated meta to the cache', async () => {
nock(registries.default)
.get('/is-positive')

View File

@@ -0,0 +1,14 @@
import { normalizeRegistryUrl } from '../lib/normalizeRegistryUrl.js'
test.each([
['https://registry.example.com:443/package.tgz', 'https://registry.example.com/package.tgz'],
['http://registry.example.com:80/package.tgz', 'http://registry.example.com/package.tgz'],
['https://registry.example.com:8443/package.tgz', 'https://registry.example.com:8443/package.tgz'],
['http://registry.example.com:8080/package.tgz', 'http://registry.example.com:8080/package.tgz'],
['https://registry.example.com/package.tgz', 'https://registry.example.com/package.tgz'],
['http://registry.example.com/package.tgz', 'http://registry.example.com/package.tgz'],
['https://artifactory:443/api/npm/npm-virtual/uuid/-/uuid-9.0.1.tgz', 'https://artifactory/api/npm/npm-virtual/uuid/-/uuid-9.0.1.tgz'],
['invalid-url', 'invalid-url'],
])('normalizeRegistryUrl(%s) should return %s', (input, expected) => {
expect(normalizeRegistryUrl(input)).toBe(expected)
})