diff --git a/.changeset/lovely-candles-report.md b/.changeset/lovely-candles-report.md new file mode 100644 index 0000000000..d566f1482c --- /dev/null +++ b/.changeset/lovely-candles-report.md @@ -0,0 +1,8 @@ +--- +"@pnpm/default-resolver": major +"@pnpm/tarball-resolver": major +"@pnpm/core": major +"pnpm": major +--- + +Dependencies specified via a URL are now recorded in the lockfile using their final resolved URL. Thus, if the original URL redirects, the final redirect target will be saved in the lockfile [#8833](https://github.com/pnpm/pnpm/issues/8833). diff --git a/.changeset/rare-singers-kneel.md b/.changeset/rare-singers-kneel.md new file mode 100644 index 0000000000..2787170799 --- /dev/null +++ b/.changeset/rare-singers-kneel.md @@ -0,0 +1,6 @@ +--- +"@pnpm/fetch": minor +"@pnpm/fetching-types": minor +--- + +The `fetch` function accepts a `method` option now. diff --git a/network/fetch/src/fetchFromRegistry.ts b/network/fetch/src/fetchFromRegistry.ts index accf121306..3a9a836114 100644 --- a/network/fetch/src/fetchFromRegistry.ts +++ b/network/fetch/src/fetchFromRegistry.ts @@ -67,6 +67,7 @@ export function createFetchFromRegistry ( }, // if verifying integrity, node-fetch must not decompress compress: opts?.compress ?? false, + method: opts?.method, headers, redirect: 'manual', retry: opts?.retry, diff --git a/network/fetching-types/src/index.ts b/network/fetching-types/src/index.ts index ffc3d730e3..ecaf30e255 100644 --- a/network/fetching-types/src/index.ts +++ b/network/fetching-types/src/index.ts @@ -1,11 +1,16 @@ import { type RetryTimeoutOptions } from '@zkochan/retry' -import { type Response } from 'node-fetch' +import { type Response, type RequestInit as NodeRequestInit } from 'node-fetch' export type { RetryTimeoutOptions } +export interface RequestInit extends NodeRequestInit { + retry?: RetryTimeoutOptions + timeout?: number +} + export type FetchFromRegistry = ( url: string, - opts?: { + opts?: RequestInit & { authHeaderValue?: string compress?: boolean retry?: RetryTimeoutOptions diff --git a/pkg-manager/core/test/lockfile.ts b/pkg-manager/core/test/lockfile.ts index b072f7d07e..758ddb2bde 100644 --- a/pkg-manager/core/test/lockfile.ts +++ b/pkg-manager/core/test/lockfile.ts @@ -1118,6 +1118,9 @@ test('tarball domain differs from registry domain', async () => { }) test('tarball installed through non-standard URL endpoint from the registry domain', async () => { + nock('https://registry.npmjs.org', { allowUnmocked: true }) + .head('/is-positive/download/is-positive-3.1.0.tgz') + .reply(200, '') nock('https://registry.npmjs.org', { allowUnmocked: true }) .get('/is-positive/download/is-positive-3.1.0.tgz') .replyWithFile(200, tarballPath) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 6227dc7bbc..5f50f44ea9 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -6517,10 +6517,16 @@ importers: resolving/tarball-resolver: dependencies: + '@pnpm/fetching-types': + specifier: workspace:* + version: link:../../network/fetching-types '@pnpm/resolver-base': specifier: workspace:* version: link:../resolver-base devDependencies: + '@pnpm/fetch': + specifier: workspace:* + version: link:../../network/fetch '@pnpm/tarball-resolver': specifier: workspace:* version: 'link:' diff --git a/resolving/default-resolver/src/index.ts b/resolving/default-resolver/src/index.ts index 4c17a91c41..b97a69436d 100644 --- a/resolving/default-resolver/src/index.ts +++ b/resolving/default-resolver/src/index.ts @@ -30,7 +30,7 @@ export function createResolver ( resolve: async (wantedDependency, opts) => { const resolution = await resolveFromNpm(wantedDependency, opts as ResolveFromNpmOptions) ?? (wantedDependency.pref && ( - await resolveFromTarball(wantedDependency as { pref: string }) ?? + await resolveFromTarball(fetchFromRegistry, wantedDependency as { pref: string }) ?? await resolveFromGit(wantedDependency as { pref: string }) ?? await resolveFromLocal(wantedDependency as { pref: string }, opts) )) diff --git a/resolving/tarball-resolver/package.json b/resolving/tarball-resolver/package.json index 386b6f2e05..b8b26d2c25 100644 --- a/resolving/tarball-resolver/package.json +++ b/resolving/tarball-resolver/package.json @@ -26,6 +26,7 @@ }, "homepage": "https://github.com/pnpm/pnpm/blob/main/resolving/tarball-resolver#readme", "dependencies": { + "@pnpm/fetching-types": "workspace:*", "@pnpm/resolver-base": "workspace:*" }, "funding": "https://opencollective.com/pnpm", @@ -33,6 +34,7 @@ "pnpm10" ], "devDependencies": { + "@pnpm/fetch": "workspace:*", "@pnpm/tarball-resolver": "workspace:*" }, "exports": { diff --git a/resolving/tarball-resolver/src/index.ts b/resolving/tarball-resolver/src/index.ts index 24142e61ca..e6e9fdb914 100644 --- a/resolving/tarball-resolver/src/index.ts +++ b/resolving/tarball-resolver/src/index.ts @@ -1,6 +1,8 @@ import { type PkgResolutionId, type ResolveResult } from '@pnpm/resolver-base' +import { type FetchFromRegistry } from '@pnpm/fetching-types' export async function resolveFromTarball ( + fetchFromRegistry: FetchFromRegistry, wantedDependency: { pref: string } ): Promise { if (!wantedDependency.pref.startsWith('http:') && !wantedDependency.pref.startsWith('https:')) { @@ -9,11 +11,14 @@ export async function resolveFromTarball ( if (isRepository(wantedDependency.pref)) return null + // If there are redirects, we want to get the final URL address + const { url: resolvedUrl } = await fetchFromRegistry(wantedDependency.pref, { method: 'HEAD' }) + return { - id: wantedDependency.pref as PkgResolutionId, - normalizedPref: wantedDependency.pref, + id: resolvedUrl as PkgResolutionId, + normalizedPref: resolvedUrl, resolution: { - tarball: wantedDependency.pref, + tarball: resolvedUrl, }, resolvedVia: 'url', } diff --git a/resolving/tarball-resolver/test/index.ts b/resolving/tarball-resolver/test/index.ts index 4c15f0541a..90e5247170 100644 --- a/resolving/tarball-resolver/test/index.ts +++ b/resolving/tarball-resolver/test/index.ts @@ -1,22 +1,28 @@ /// // cspell:ignore buildserver -import { resolveFromTarball } from '@pnpm/tarball-resolver' +import { resolveFromTarball as _resolveFromTarball } from '@pnpm/tarball-resolver' +import { createFetchFromRegistry } from '@pnpm/fetch' + +const fetch = createFetchFromRegistry({}) +const resolveFromTarball = _resolveFromTarball.bind(null, fetch) test('tarball from npm registry', async () => { const resolutionResult = await resolveFromTarball({ pref: 'http://registry.npmjs.org/is-array/-/is-array-1.0.1.tgz' }) expect(resolutionResult).toStrictEqual({ - id: 'http://registry.npmjs.org/is-array/-/is-array-1.0.1.tgz', - normalizedPref: 'http://registry.npmjs.org/is-array/-/is-array-1.0.1.tgz', + id: 'https://registry.npmjs.org/is-array/-/is-array-1.0.1.tgz', + normalizedPref: 'https://registry.npmjs.org/is-array/-/is-array-1.0.1.tgz', resolution: { - tarball: 'http://registry.npmjs.org/is-array/-/is-array-1.0.1.tgz', + tarball: 'https://registry.npmjs.org/is-array/-/is-array-1.0.1.tgz', }, resolvedVia: 'url', }) }) test('tarball from URL that contain port number', async () => { - const resolutionResult = await resolveFromTarball({ pref: 'http://buildserver.mycompany.com:81/my-private-package-0.1.6.tgz' }) + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const fetch: any = async (url: string) => ({ url }) + const resolutionResult = await _resolveFromTarball(fetch, { pref: 'http://buildserver.mycompany.com:81/my-private-package-0.1.6.tgz' }) expect(resolutionResult).toStrictEqual({ id: 'http://buildserver.mycompany.com:81/my-private-package-0.1.6.tgz', @@ -32,10 +38,10 @@ test('tarball not from npm registry', async () => { const resolutionResult = await resolveFromTarball({ pref: 'https://github.com/hegemonic/taffydb/tarball/master' }) expect(resolutionResult).toStrictEqual({ - id: 'https://github.com/hegemonic/taffydb/tarball/master', - normalizedPref: 'https://github.com/hegemonic/taffydb/tarball/master', + id: 'https://codeload.github.com/hegemonic/taffydb/legacy.tar.gz/refs/heads/master', + normalizedPref: 'https://codeload.github.com/hegemonic/taffydb/legacy.tar.gz/refs/heads/master', resolution: { - tarball: 'https://github.com/hegemonic/taffydb/tarball/master', + tarball: 'https://codeload.github.com/hegemonic/taffydb/legacy.tar.gz/refs/heads/master', }, resolvedVia: 'url', }) @@ -45,10 +51,10 @@ test('tarballs from GitHub (is-negative)', async () => { const resolutionResult = await resolveFromTarball({ pref: 'https://github.com/kevva/is-negative/archive/1d7e288222b53a0cab90a331f1865220ec29560c.tar.gz' }) expect(resolutionResult).toStrictEqual({ - id: 'https://github.com/kevva/is-negative/archive/1d7e288222b53a0cab90a331f1865220ec29560c.tar.gz', - normalizedPref: 'https://github.com/kevva/is-negative/archive/1d7e288222b53a0cab90a331f1865220ec29560c.tar.gz', + id: 'https://codeload.github.com/kevva/is-negative/tar.gz/1d7e288222b53a0cab90a331f1865220ec29560c', + normalizedPref: 'https://codeload.github.com/kevva/is-negative/tar.gz/1d7e288222b53a0cab90a331f1865220ec29560c', resolution: { - tarball: 'https://github.com/kevva/is-negative/archive/1d7e288222b53a0cab90a331f1865220ec29560c.tar.gz', + tarball: 'https://codeload.github.com/kevva/is-negative/tar.gz/1d7e288222b53a0cab90a331f1865220ec29560c', }, resolvedVia: 'url', }) diff --git a/resolving/tarball-resolver/tsconfig.json b/resolving/tarball-resolver/tsconfig.json index f24e79a0aa..ebb0a2ae33 100644 --- a/resolving/tarball-resolver/tsconfig.json +++ b/resolving/tarball-resolver/tsconfig.json @@ -9,6 +9,12 @@ "../../__typings__/**/*.d.ts" ], "references": [ + { + "path": "../../network/fetch" + }, + { + "path": "../../network/fetching-types" + }, { "path": "../resolver-base" }