diff --git a/.changeset/solid-regions-ring.md b/.changeset/solid-regions-ring.md new file mode 100644 index 0000000000..6cf4c8b2f0 --- /dev/null +++ b/.changeset/solid-regions-ring.md @@ -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). diff --git a/resolving/npm-resolver/src/index.ts b/resolving/npm-resolver/src/index.ts index d7f9a13515..4aa3a14fd4 100644 --- a/resolving/npm-resolver/src/index.ts +++ b/resolving/npm-resolver/src/index.ts @@ -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, diff --git a/resolving/npm-resolver/src/normalizeRegistryUrl.ts b/resolving/npm-resolver/src/normalizeRegistryUrl.ts new file mode 100644 index 0000000000..171b858ab9 --- /dev/null +++ b/resolving/npm-resolver/src/normalizeRegistryUrl.ts @@ -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 + } +} diff --git a/resolving/npm-resolver/test/index.ts b/resolving/npm-resolver/test/index.ts index 24e3852334..992d738807 100644 --- a/resolving/npm-resolver/test/index.ts +++ b/resolving/npm-resolver/test/index.ts @@ -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') diff --git a/resolving/npm-resolver/test/normalizeRegistryUrl.test.ts b/resolving/npm-resolver/test/normalizeRegistryUrl.test.ts new file mode 100644 index 0000000000..5ad05234ae --- /dev/null +++ b/resolving/npm-resolver/test/normalizeRegistryUrl.test.ts @@ -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) +})