From b5f794646a476bfcf75d9b581f04f3141f3bafe5 Mon Sep 17 00:00:00 2001 From: Paul Bouchon Date: Sun, 12 Jul 2026 07:32:03 -0400 Subject: [PATCH] fix: strip trailing slash from COREPACK_NPM_REGISTRY A `COREPACK_NPM_REGISTRY` value with a trailing slash produced request URLs with a double slash (e.g. `https://registry.npmjs.org//pnpm`). registry.npmjs.org tolerates this, but some registries such as registry.npmmirror.com reject it with a 404. Normalize the registry URL by removing trailing slashes before building request URLs. Fixes: https://github.com/nodejs/corepack/issues/711 Signed-off-by: Paul Bouchon --- sources/npmRegistryUtils.ts | 5 ++++- tests/npmRegistryUtils.test.ts | 9 +++++++++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/sources/npmRegistryUtils.ts b/sources/npmRegistryUtils.ts index a7d110e95..2666ccd96 100644 --- a/sources/npmRegistryUtils.ts +++ b/sources/npmRegistryUtils.ts @@ -14,7 +14,10 @@ export const DEFAULT_HEADERS: Record = { export const DEFAULT_NPM_REGISTRY_URL = `https://registry.npmjs.org`; export async function fetchAsJson(packageName: string, version?: string) { - const npmRegistryUrl = process.env.COREPACK_NPM_REGISTRY || DEFAULT_NPM_REGISTRY_URL; + // Strip any trailing slashes so a `COREPACK_NPM_REGISTRY` with a trailing + // slash does not produce a double slash in the request URL (some registries, + // e.g. registry.npmmirror.com, reject `//package` with a 404). + const npmRegistryUrl = (process.env.COREPACK_NPM_REGISTRY || DEFAULT_NPM_REGISTRY_URL).replace(/\/+$/, ``); if (process.env.COREPACK_ENABLE_NETWORK === `0`) throw new UsageError(`Network access disabled by the environment; can't reach npm repository ${npmRegistryUrl}`); diff --git a/tests/npmRegistryUtils.test.ts b/tests/npmRegistryUtils.test.ts index 728d6f977..0ba9e8740 100644 --- a/tests/npmRegistryUtils.test.ts +++ b/tests/npmRegistryUtils.test.ts @@ -35,6 +35,15 @@ describe(`npm registry utils fetchAsJson`, () => { expect(httpFetchAsJson).lastCalledWith(`${process.env.COREPACK_NPM_REGISTRY}/package-name`, {headers: DEFAULT_HEADERS}); }); + it(`does not produce a double slash when COREPACK_NPM_REGISTRY has a trailing slash`, async () => { + // `process.env` is reset after each tests in setupTests.js. + process.env.COREPACK_NPM_REGISTRY = `https://registry.example.org/`; + await fetchAsJson(`package-name`); + + expect(httpFetchAsJson).toBeCalled(); + expect(httpFetchAsJson).lastCalledWith(`https://registry.example.org/package-name`, {headers: DEFAULT_HEADERS}); + }); + it(`adds authorization header with bearer token if COREPACK_NPM_TOKEN is set`, async () => { // `process.env` is reset after each tests in setupTests.js. process.env.COREPACK_NPM_TOKEN = `foo`;